122
Bogdan PĂTRUŢ Iulian Marius FURDU Grafică 3D, animaţie şi jocuri EduSoft 2005

Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

  • Upload
    others

  • View
    11

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

Bogdan PĂTRUŢ Iulian Marius FURDU

Grafică 3D, animaţie şi jocuri

EduSoft 2005

Page 2: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

2

Redactor: Tiberiu SOCACIU

0Descrierea CIP a Bibliotecii Naţionale a României

PĂTRUŢ, BOGDAN Grafica 3D : animaţie şi jocuri / Bogdan Pătruţ, Iulian Marius Furdu. - Bacău : EduSoft, 2005 Bibliogr. ISBN 973-87496-2-X I. Furdu, Iulian Marius 004.42C

Copyright © 2005 Editura EduSoft Toate drepturile asupra prezentei ediţii sunt rezervate Editurii EduSoft. Reproducerea parţială sau integrală a conţinutului, prin orice mijloc, fără acordul scris al Editurii EduSoft este interzisă şi se va pedepsi conform legislaţiei în vigoare. Editura EduSoft 600065 Bacău, str. 9 Mai, nr. 82, sc. C, ap. 13 E-mail: [email protected], Web: www.edusoft.ro ISBN 973-87496-2-X

Page 3: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

3

Bogdan PĂTRUŢ Iulian Marius FURDU [email protected] [email protected]

Grafică 3D, animaţie şi jocuri surse de programe în C++ şi Pascal

Editura EduSoft Bacău 2005

Page 4: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

4

CUPRINS Introducere 5 Capitolul 1. Puţină geometrie proiectivă. Programul High - 3D 7

1.1. Translaţii. Rotaţii 7 1.2. Programul High – 3D 9

1.2.1. Bazele teoretice 9 1.2.2. Structurile de date folosite 13 1.2.3. Utilizarea programului 15 1.2.4. Listingul comentat al programului 16

Capitolul 2. Animaţie profesională 37

2.1. Fişierele de animaţie FLI 37 2.2. Programul PLFLI pentru animat fişiere FLI 40

Capitolul 3. Afişări deosebite, folosind fişierele de caractere 45

3.1. Formatul fonturilor CHR 45 3.2. Rutine speciale de afişare 47

Capitolul 4. Despre jocurile pe calculator 51

4.1. Ce presupune realizarea unui joc pe calculator 51 4.2. Ce jocuri putem realiza împreună, pe calculator ? 52

Capitolul 5. Jocuri folosind unit-ul CRT 57

5.1. Animaţie la “Turnurile din Hanoi” 57 5.2. Bila 61 5.3. Navele 66

Capitolul 6. Jocuri folosind unit-ul GRAPH 73

6.1. Tetris 73 6.2. Feţe 83 6.3. Transformări de imagini 90

Capitloul 7. Jocuri cu prelucrări de fişiere BMP, în 256 de culori 101

7.1. Amestec 101 7.2. Spânzurătoarea 104

Anexa 1. Fişierele de ajutor ale programului High-3D 108 Anexa 2. Uniturile uMouse, MCGA şi ViewBMP 110 Bibliografie 122

Page 5: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

5

Pentru Ştefan, Monica şi Cristina

Introducere

Grafica este, fără îndoială, unul din cele mai interesante domenii ale utilizării calculatoarelor. Când spunem “grafică pe calculator” ne referim atât la grafica bidimensională (programe de desenare ca PaintBrush sau Corel), cât şi la proiectarea asistată de calculator sau programele de animaţie, chiar şi la jocuri. Este de ajuns să amintim trei din cele mai utilizate produse soft, realizate de un grup al companiei Autodesk: Animator, 3D Studio şi Auto-CAD. Primele două reprezintă programe complexe şi performante de animaţie bi-, respectiv tridimensională, de pe calculatoarele compatibile cu IBM-PC. Cu ajutorul lor se poate realiza animaţie de orice gen, de la simple spoturi publicitare, până la unele desene animate de scurt metraj. Cel de al treilea este un standard în domeniul produselor soft de tip CAD (proiectare asistată de calculator), fiind folosit şi în ţara noastră de foarte mulţi ingineri în proiectarea, realizarea şi dezvoltarea aplicaţiilor inginereşti necesare lor, indiferent de domeniul lor de activitate. Suntem convinşi că toţi cei care deschid copertele acestei cărţi au avut nu o dată ocazia de a “se juca” cu unul din cele trei programe amintite. Însă cititorii sunt conştienţi că în spatele acestor programe stau mii de formule de geometrie analitică, descriptivă şi proiectivă, precum şi algoritmi de compresie a datelor, la care se adaugă un mare efort intelectual şi multă, multă muncă. Adresându-se tuturor programatorilor care doresc să pătrundă puţin în tainele graficii tridimensionale şi ale animaţiei profesionale, fie că ei sunt elevi sau studenţi, informaticieni din producţie sau profesori de informatică, matematicieni sau ingineri, cu toţii având, însă, cunoştinţe de programare în limbajul C, primele trei capitole ale lucrării de faţă le pune la dispoziţie materiale teoretice suficiente pentru a realiza două aplicaţii de gen, destul de complexe. Sunt prezentate şi două programe demonstrative, bine comentate, care pun în practică aceste cunoştinţe teoretice. Primul este un editor - vizualizator de corpuri tridimensionale, iar cel de al doilea un decodificator şi animator de fişiere de animaţie FLI, create de Autodesk Animator. Cartea poate fi considerată un supliment la orice lucrare de grafică în limbajele C şi C++. Fără pretenţia de a se ridica la nivelul lui Auto-CAD, programul HIGH-3D vă permite crearea şi vizualizarea din diferite unghiuri a unor corpuri tridimensionale, de la cele mai simple până la cele mai complexe, a căror realizare depinde de ingeniozitatea, experienţa şi, nu în ultimul rând, de dexteritatea celui ce le creează. În varianta prezentată în paginile cărţii, programul este sub o formă simplificată, aspectul urmărit de noi fiind cel didactic. Programul poate fi extins de către cititor şi chiar îi recomandăm aceasta, pentru a face din el un produs soft de grafică tridimensională cât mai performant. În ceea ce priveşte programul PLFLI, acesta pune în practică algoritmul de decompresie şi vizualizare a unui fişier de animaţie, el poate fi perfecţionat, de asemenea, de cititor, în vederea realizării unor optimizări ale operaţiilor de citire de pe disc, etc. Al treilea capitol al cărţii se ocupă cu domeniul afişărilor în mod grafic, prezentându-se formatul fişierelor CHR, de la care se pleacă în dezvoltarea de funcţii de scriere specială (în mod grafic). Credem că cititorul este conştient de importanţa pe care o au jocurile în educarea omului, încă din anii copilăriei. Calculatoarele, prin posibilităţile lor de a face rapid o serie întreagă de calcule şi de a reprezenta grafic diferite obiecte din natură, pot face aşa încât să simuleze pe ecranele lor aproape orice joc care poate fi practicat, de la jocurile de cărţi sau zaruri, până la şah sau go, de la jocurile cu cuvinte, până chiar la jocurile sportive, deoarece pe calculator pot fi simulate şi animate o serie întreagă de activităţi din lumea reală, din natură. Putem spune, astfel, că jocurile pe calculator reprezintă în mic, pe ecran, lumea mare reală. Suntem fermi convinşi că cititorul acestei cărţi a văzut şi a şi jucat multe jocuri pe calculatoare personale şi chiar a şi încercat să realizeze singur, simple astfel de jocuri. Dacă nu a

Page 6: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

6

reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de noi, poate va reuşi să realizeze unele jocuri interesante şi inedite, care vor impresiona pe cei din cercul său de prieteni pasionaţi de informatică. Astfel, lucrarea de faţă se adresează şi acelora care, cunoscând limbajul Turbo Pascal, având destulă experienţă în programare şi cunoscând procedurile grafice pe care acest mediu de programare le oferă, şi-au pus de multe ori întrebarea “Cum se face acest lucru?”, atunci când au văzut un joc sau altul, realizat de firme vestice de renume în acest domeniu. Este o carte pentru tinerii pasionaţi de informatică, de grafică, dar şi de jocuri, iar unit-urile şi programele din paginile de faţă pot fi folosite cu succes şi de alte categorii de programatori. Jocurile din carte sunt de trei feluri: în modul text, în modul grafic EGA sau VGA şi în modul grafic MCGA. Programele de grafică din lucrare folosesc trei unit-uri de bază, care sunt prezentate în anexa de la sfârşitul cărţii: uMouse - pentru lucrul cu mouse-ul în modurile grafice, MCGA - cuprinzând proceduri grafice elementare pentru grafica în 256 de culori, a modului grafic MCGA (200 × 200 pixeli) şi ViewBMP care prelucrează imagini BMP, în diferite moduri grafice. Formatul unui fişier BMP este prezentat în unit-ul BMPTypes, din aceeaşi anexă. Sperăm ca lucrarea să fie de un real folos tuturor pasionaţilor şi specialiştilor în asemenea aplicaţii complexe de grafică, animaţie şi jocuri pe calculator.

Autorii

Page 7: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

7

Capitolul 1. Puţină geometrie proiectivă. Programul High - 3D

1.1 Translaţii. Rotaţii. În principiu, ne propunem să realizăm un program pe calculator, care să editeze grafic un corp, pe care apoi să-l proiecteze în plan, să-l vizualizeze şi să-l rotească sub diferite unghiuri, faţă de una din cele trei axe de coordonate, prin punctul de coordonate (0,0,0). Să considerăm, aşadar un sistem de coordonate spaţiale, OXYZ şi un punct (x,y,z). Dacă dorim să realizăm translaţii şi rotaţii ale acestui punct, vom folosi o formulare matricială, în care matricea (x1,y1,z1,1) a punctului obţinut în urma aplicării unei transformări, să se obţină din matricea punctului iniţial (x,y,z), înmulţită matricial cu o matrice de dimensiune 4 x 4. A patra componentă (1), apare pentru uşurinţa calculelor. • Astfel, dacă dorim să translatăm punctul de coordonate (x,y,z) într-un nou punct (x1,y1,z1), transformarea ce trebuie aplicată este: 1 0 0 0 (x1,y1,z1) = (x,y,z,1) 0 1 0 0 0 0 1 0 Tx Ty Tz 1 , unde Tx, Ty şi Tz sunt componente ale translaţiei în direcţiile X, Y şi Z, adică deplasările pe cele trei direcţii. • Transformările rotaţiei în spaţiul tridimensional sunt mai complexe, fiind necesară determinarea unei axe de rotaţie. Specificarea axei de rotaţie include atât direcţia cât şi localizarea. Este necesar să se definească rotaţiile în raport cu cele trei axe ale reperului Ox, Oy, Oz. Rotaţia în jurul axei Ox prin punctul (0,0,0) este: 1 0 0 0 (x1,y1,z1,1) = (x,y,z,1) 0 cos a sin a 0 0 -sin a cos a 0 0 0 0 1 Din egalarea celor doi membri şi identificarea necunoscutelor obţinem formulele: x1 = x, y1 = y cos a - z sin a şi z1 = y sin a + z cos a. Unghiul de rotaţie a este măsurat în sens orar în jurul originii, privind originea dintr-un punct de pe axa X pozitivă. Coordonata x nu este, evident, afectată.

În mod similar se obţin formulele pentru rotaţii în jurul celorlalte două axe. • Dar, înainte de a-l roti, un corp va trebui să fie reprezentat planar, pe ecran. Coordonatele ecranului, fiind numere întregi originea fiind în colţul stânga-sus (abscisa crescând de la stânga la dreapta, iar

Page 8: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

8

ordonata de sus în jos), de acest lucru va trebui să se ţină cont în scrierea formulelor de calcul în cadrul programului. Deocamdată vom considera că lucrăm cu numere reale şi cu sisteme de coordonate obişnuite. Generarea unei imagini în perspectivă revine la a diviza coordonatele X şi Z ale punctului prin profunzimea punctului, care de fapt, este ordonata sa Y. Punctul R(xr,yr,zr) va avea ca perspectivă punctul P(a,b), în planul ecranului. Considerăm un sistem de coordonate OXYZ, situat între corpul respectiv şi ochiul observatorului, un plan de proiecţie (ecranul) şi punctul R (aparţinând corpului), proiectat în P, pe acest plan.

Observăm că avem: PP1 || RR1 => Δ OPP1 ~ Δ ORR1 => OP / OR = OP1 / OR1 = O’P1 / QR1. PP2 || RR2 => Δ OPP2 ~ Δ ORR2 => OP / OR = OP2 / OR2 = O’P2 / QR2. Dar, cum şi O’P || QR, rezultă că şi Δ OPO’ ~ Δ ORQ => OP / OR = OO’ / OQ. Avem un şir de rapoarte egale, din care obţinem:

b / zr = a / xr = OO’ / OQ = (ymax - yr) / ymax, în care:

ymax = d(O,Q), adică distanţa dintre observator şi punctul Q, corespunzător punctului R, (xr,yr,zr) = coordonatele lui R faţă de sistemul de coordonate tridimensional dat, iar (a,b) sunt coordonatele proiecţiei sale (deci ale lui P), în planul bidimensional, situat între ochiul observatorului şi punctul R. Formulele prezentate anterior se regăsesc, cu adaptările de rigoare, în cadrul funcţiilor RotireOX, respectiv ProiecteazăCorpul din programul de care se ocupă capitolul următor.

Page 9: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

9

1.2. Programul High - 3D În urma unei munci susţinute, de cercetare, proiectare şi programare, în perioada septembrie 1993 - ianuarie 1994, am realizat un program de grafică interactivă tridimensională, care permite utilizatorului să creeze, iar apoi să vizualizeze, pe ecranul calculatorului, corpuri tridimensionale. De asemenea, programul, numit High-3D, permite translarea, rotirea şi redimensionarea corpului, precum şi imprimarea pe hârtie a imaginii sale grafice în două dimensiuni. Sistemul lucrează interactiv, având o interfaţă foarte prietenoasă, cu comenzi de tastatură sau de mouse şi cu un sistem de “help” (ajutor). În cele ce urmează vom prezenta bazele teoretice ale produsului amintit, iar la sfârşit vom pune la dispoziţia cititorului o variantă mult redusă a programului, însă mult mai elegant realizată şi prezentată. Această variantă a fost realizată de curând şi este scrisă în limbajul C (Borland C++ 3.1).

1.2.1. Bazele teoretice

Ideea care stă la baza editării de corpuri tridimensionale, folosind un spaţiu de lucru bidimensional (suprafaţa ecranului) este următoarea: Un corp tridimensional poate fi reprezentat prin secţiuni în planul xOy, de-a lungul axei Oz, secţiuni paralele şi legate (în general) între ele:

Astfel, pentru a construi un corp vom desena diferite secţiuni xOy, plimbându-ne de-a lungul lui Oz şi unind punctele corespunzătoare între ele: 1 cu 1', 2 cu 2' etc.. Probleme apar atunci când se doreşte crearea unor corpuri, ca de exemplu:

Page 10: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

10

Programul High-3D permite rezolvarea acestui gen de probleme, folosind funcţii speciale, astfel: (a) În cazul piramidei nu se va desena în secţiunea 1 un triunghi, iar în a doua un punct, deoarece trasarea muchiilor de legătură între secţiuni se face automat, High-3D unind 1' cu 1, cu 2, până la minimul dintre numărul de puncte dintr-o secţiune şi numărul corespunzător celeilalte.

Pentru obţinerea unei piramide se va proceda astfel: - fie se va copia triunghiul (1'2'3') în secţiunea a doua, apoi se va micşora suficient de mult pentru a crea impresia unui punct:

Page 11: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

11

- fie se va desena în secţiunea a doua un triunghi (1'2'3') în care colţurile să coincidă. (b) În cazul cilindrului, problema care apare este cea a trasării generatoarelor. Numărul lor poate fi ales de utilizator şi reprezintă numărul de laturi ale poligonului regulat care va aproxima fiecare din cercurile de bază:

Combinând cu cele spuse la punctul (a) se obţine usor metoda de trasare a unui con sau a unui trunchi de con. (c) Pentru a crea cilindrul deasupra cubului se vor folosi patru secţiuni, dintre care, cele două din mijloc vor avea aceeaşi valoare pentru cotă (z). High-3D nu va proceda la unirea punctelor, ci va considera că de acolo începe un alt corp, suprapus peste primul:

(d) Pentru a construi corpuri separate, se va folosi acelaşi principiu explicat la punctul (a), considerând însă între cele două corpuri o secţiune cu numărul de puncte nul:

Page 12: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

12

(e) Pentru a desena corpuri drepte, ca în primele patru cazuri, trebuie ca pătratele, cercurile şi triunghiurile din secţiuni să aibă o axă de simetrie paralelă cu Oz (de pildă toate secţiunile să fie centrate în originea lui xOy), iar pentru a crea corpuri oblice precum cel din cazul (e), trebuie să nu mai fie îndeplinită această condiţie pentru toate secţiunile.

Page 13: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

13

1.2.2. Structurile de date folosite

În programul HIGH-3D am definit următoarele tipuri de date: • TPunct - pentru a reprezenta punctele din spaţiu, fiecare punct având specificate cele trei coordonate ale sale; • TCoord - pentru reprezentarea punctelor din plan, date prin doar două coordonate; • TLinie - pentru reprezentarea unei linii, dată prin numerele de ordine ale punctelor extremităţi; • TPunct2 - pentru a reprezenta puncte din plan, dar din cadrul secţiunilor corpului editat; • TLatura - pentru reprezentarea acelor laturi din fiecare secţiune a corpului; • TSecţiune - pentru a reprezenta ansamblul acestor laturi precum şi numărul lor, într-o secţiune; Aceste tipuri de date sunt definite după cum urmează. De asemenea, am dat şi lista constantelor şi variabilelor globale folosite în reprezentările grafice din program. Astfel, punctele corpului sunt memorate în “mulţimea” de puncte MPuncte, iar liniile ce le unesc sunt date în MLinie. Cotele secţiunilor sunt toate memorate în vectorul Z. MPro este mulţimea punctelor proiecţie. MLinie vor uni aceste puncte, corespunzător cu MPuncte. Astfel, dacă MPuncte[i] şi MPuncte[j] sunt unite, atunci şi MPro[i] şi MPro[j] vor fi unite. • const NrMaxPuncte=100;

const NrMaxLinii=1000; const NrMaxSectiuni=20; const NrMaxLatS=50; // numărul maxim de laturi dintr-o secţiune

struct TPunct { int x,y,z; } MPuncte[NrMaxPuncte+1]; // punct 3D struct TCoord { int a,b; } MPro[NrMaxPuncte+1]; // punct de proiecţie

// linie între două puncte numerotate cu p şi q struct TLinie { int p,q; } MLinii[NrMaxLinii+1];

struct TPunct2 { int x,y; }; // o latură dintr-o secţiune are două puncte struct TLatura { TPunct2 p1,p2; };

// o secţiune este o mulţime de NrLaturi laturi:

struct TSectiune { int NrLaturi; TLatura Latura[NrMaxLatS+1]; };

// secţiunea în care se desenează

int SectCurenta; // cotele tuturor secţiunilor

int Z[NrMaxSectiuni+1]; // cota secţiunii curente şi cota secţiunii viitoare

int CotaCurenta, CotaViitoare; // vectorul secţiunilor

TSectiune MSect[NrMaxSectiuni+1]; // numărul de puncte ale corpului, precum şi numărul de linii // care leagă aceste puncte între ele, doua câte două:

int NrPuncte, NrLinii; Un experimentat în programare, având un stil de lucru cât mai elegant, va înţelege uşor motivul definirii structurii de meniu ca mai jos:

Page 14: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

14

• // Definirea structurii de meniu: const MaxCom = 12; // numărul maxim de comenzi din meniuri struct TMeniu { int NrCom; // numărul de comenzi ale meniului char * NumeCom[MaxCom+1]; // numele acestora };

Comenzilor li se ataşează constante simbolice, indicând numărul lor în cadrul meniurilor din care fac parte. O constantă aparte este definită pentru comanda “nimic”.

• // constantă reprezentând neselectarea vreunei comenzi din meniuri

const cmNimic=0;

Page 15: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

15

1.2.3. Utilizarea programului

Editarea de corpuri tridimensionale nu este întotdeauna un lucru uşor. Ideea generării corpului prin secţiuni, folosită de programul HIGH - 3D, a fost preluată dintr-un program care circula pe micro-calculatoarele familiale de tip Spectrum. Este vorba despre produsul VU-3D, al firmei PSION. Fiecare metodă de editare tridimensională are şi avantaje şi dezavantaje. Această metodă, în afara faptului că este facil de implementat, prezintă avantajul unei foarte simple creări a corpurilor cu anumite simetrii, dacă respectivul corp este bine imaginat. Pentru a explica cum se utilizează programul, vom considera două corpuri şi vă vom spune cum se pot desena ele: ♦ O masă dreptunghiulară, cu patru picioare prismatice: Nimic mai simplu. Se desenează un dreptunghi centrat în centrul ecranului. (Tehnica de desenare a unui dreptunghi o presupunem cunoscută. Dacă nu, ea se poate deduce din procedura din program.). Apoi se măreşte cota Z, apăsând de mai multe ori pe “Z+”. Se trece la o nouă cotă, apăsând “Schimbă Z”. În acest moment o nouă secţiune, vidă, ia naştere. Se copiază în această secţiune, prima secţiune. Deci se va apăsa “Copie”. Acum se acţionează “Schimbă Z” din nou. Se suprascrie, în acest fel, o a treia secţiune, peste cea de a doua, dar având aceeaşi cotă cu ea. Aici se desenează, din linii, patru poligoane regulate, în cele patru colţuri ale dreptunghiului. Se trece (cu “Z+”, de câteva ori, urmat de “Schimbă Z”) într-o nouă secţiune şi se dă comanda “Copie”. Masa e gata. Pentru a o vizualiza se acţionează butonul corespunzător. Acum deja suntem în alt meniu. Vedem masa de pe direcţia axei Y. Axa Z este sus-jos, iar axa X este stânga-dreapta. Imaginea corpului nu ne satisface, aşa că începem să îl rotim, cu comenzile în cauză. Unghiul de rotaţie se schimbă cu “Unghi -” şi “Unghi +”. Dacă nu ştim ceva, putem acţiona butonul din dreapta în dreptul comenzilor şi obţinem “help”. Corpul poate fi salvat, se poate reveni în meniul de editare, iar altă dată corpul va putea fi restaurat, din fişierul High-3D. ♦ Un pahar cu picior. Exemplul e clasic şi e împrumutat de la VU-3D. Se desenează cercuri, care se copiază unul după altul, redimensionându-l, pentru a forma mai întâi baza piciorului (două cercuri cu raze suficient de mari), apoi piciorul (două cercuri, mai mici, dar mai distanţate între ele), apoi un cerc ceva mai mare şi altul şi mai mare decât baza piciorului, distanţate şi ele, pentru a forma paharul propriu-zis.

Pentru a înţelege exact cum funcţionează acest program, va trebui să îl vedeţi. Pentru ca să-l vedeţi, va trebui să-l “butonaţi”. Deci ceea ce vă rămâne de făcut este să-l editaţi, folosind ca mediu de programare, un compilator de C al firmei Borland. De fapt, datorită modului de definire a comentariilor, va trebui să folosiţi un C++. Noi am lucrat cu Borland C++ 3.1. Dacă vă lipsiţi, însă de comentarii, e suficient un Turbo C . Spor la lucru !

Page 16: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

16

1.2.4. Listingul comentat al programului

// HIGH - 3D // *********** program exemplu de grafică tridimensională ************ // Autor: Bogdan Pătruţ, 1995 // // fişierul HIGH3D.CPP /* În prealabil se aduc în directorul curent (de lucru) următoarele fişiere: BGIOBJ.EXE şi EGAVGA.BGI, după care se dă comanda: bgiobj egavga.bgi egavga.obj [ _EGAVGA_driver ] numele nume nume numele public al driverului grafic programului driver fişier care se va afla în EGAVGA.OBJ de conversie grafic obiect (poate lipsi, acesta e implicit) Se creează apoi un proiect (HIGH3D.PRJ), cu Open project... (din meniu), în care se pune acest fişier (HIGH3D.CPP) şi fişierul EGAVGA.OBJ şi se apasă F9, pentru Make. Programul va putea fi rulat cu Ctrl-F9 sau din DOS, cu HIGH3D, chiar dacă fişierul EGAVGA.BGI nu ar exista. */ // Se includ acele headere care sunt necesare în program.

#include <stdio.h> #include <graphics.h> #include <conio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <dos.h>

// Definiţii pentru similitudini cu limbajul Pascal:

#define procedure void #define begin { #define end } #define then #define and && #define or || #define not !

// Înălţimea meniului şi a zonei de stare:

#define inaltime 15 // Distanţa observatorului faţă de corpul vizualizat:

int ymax = 14000; // Coordonatele centrului ecranului:

int Xmij,Ymij; // Funcţii assembler de lucru cu mouse-ul:

procedure MouseInit() // iniţializarea mouse-ului begin asm { mov ax,0; int 33h; mov ax,1; int 33h } end

// Afişarea cursorului de mouse

procedure MouseShow()

Page 17: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

17

begin asm { mov ax,1; int 33h } end // Ascunderea cursorului de mouse

procedure MouseHide() begin asm { mov ax,2; int 33h } end

// Citirea stării mouse-ului: // in b = butonul (0 = nici un buton, 1 = stinga, 2 = dreapta, 3 = ambele

procedure MouseData(int * b, int * x, int * y) begin int bb,xx,yy; asm { mov ax,3; int 33h; mov bb,bx; mov xx,cx; mov yy,dx } *b = bb; *x = xx; *y = yy; end

// Poziţionarea cursorului de mouse într-un anumit punct (x,y):

procedure MouseMove(int x, int y) begin asm { mov ax,4; mov cx,x; mov dx,y; int 33h } end

// Iniţilializare grafică:

procedure OpenGraph() begin if (registerbgidriver(EGAVGA_driver) < 0) then begin printf("Eroare grafic† !"); return; end int gd=DETECT, gm; initgraph(&gd,&gm,""); // calea grafică end

// Definirea structurii de meniu:

const MaxCom = 12; // numărul maxim de comenzi din meniuri struct TMeniu { int NrCom; // numărul de comenzi ale meniului char * NumeCom[MaxCom+1]; // numele acestora };

// constantă reprezentând neselectarea vreunei comenzi din meniuri const cmNimic=0;

// structurile de date folosite în reprezentarea corpului:

const NrMaxPuncte=100; const NrMaxLinii=1000; const NrMaxSectiuni=20; const NrMaxLatS=50; // numărul maxim de laturi dintr-o secţiune

struct TPunct { int x,y,z; } MPuncte[NrMaxPuncte+1]; // punct 3D struct TCoord { int a,b; } MPro[NrMaxPuncte+1]; // punct de proiecţie

// linie între două puncte numerotate cu p şi q struct TLinie { int p,q; } MLinii[NrMaxLinii+1];

struct TPunct2 { int x,y; }; // o latură dintr-o secţiune are două puncte struct TLatura { TPunct2 p1,p2; };

// o secţiune este o mulţime de NrLaturi laturi:

struct TSectiune { int NrLaturi; TLatura Latura[NrMaxLatS+1]; };

// secţiunea în care se desenează int SectCurenta;

// cotele tuturor secţiunilor

Page 18: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

18

int Z[NrMaxSectiuni+1]; // cota secţiunii curente şi cota secţiunii viitoare

int CotaCurenta, CotaViitoare; // vectorul secţiunilor

TSectiune MSect[NrMaxSectiuni+1]; // numărul de puncte ale corpului, precum şi numărul de linii // care leagă aceste puncte între ele, doua câte două:

int NrPuncte, NrLinii; // pentru reprezentarea tridimensională, avem aceasta variabilă // Axe3D care indică dacă se trasează şi axele sau nu, precum şi // variabila Unghi, a cărei valoare indică unghiul pentru rotiri:

int Axe3D, Unghi; // pentru editarea corpului avem un coeficient de redimensionare:

double coef; // comenzile actuală şi veche alese din meniu:

int Comanda, ComandaVeche; // funcţie necesară ieşirii forţate din program, // la apăsarea tastei Esc

int SeApasaEscape() begin if (kbhit()) then begin if (getch() == 27) then begin closegraph(); exit(1); return 1; end else return 0; end else return 0; end

// Urmează funcţii de interes general: // funcţie care verifică apartenenţa unui punct (x,y) // la interiorul unui dreptunghi, având colţul stânga-sus (x1,y1) şi // colţul dreapta-jos (x2,y2); se foloseşte pentru meniuri

int Apartine(int x,int y, int x1, int y1, int x2, int y2) begin return ((x1<=x) and (x<=x2) and (y1<=y) and (y<=y2)); end

// funcţie de rotunjire la cel mai apropiat întreg:

int Round(double x) begin int xi1 = int(x); int xi2 = xi1+1; if (double(x-double(xi1)) < double(double(xi2)-x)) then return xi1; else return xi2; end

// minimul a două numere întregi

int min(int x,int y) begin if (x<y) then return x; else return y; end

// emiterea unui semnal sonor procedure Beep() begin sound(300); delay(20); sound(350); delay(20); nosound(); end

Page 19: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

19

// conversie coordonate ecran (x,y) în real, // ţinând cont de mijlocul ecranului:

int XReal(int x) begin return (x - Xmij); end int YReal(int y) begin return (Ymij - y); end

// afişarea unui şir în zona de stare:

procedure AfisJos(int culoare, char * sir) begin // alegerea atributelor textului settextjustify(LEFT_TEXT,TOP_TEXT); // selectarea şi stergerea zonei din josul ecranului: setviewport(0,getmaxy()-inaltime,getmaxx(),getmaxy(), 1); clearviewport(); setviewport(0,0,getmaxx(),getmaxy(), 1); // desenarea unui fel de buton pe care se afişează: setfillstyle(SOLID_FILL,CYAN); bar(0,getmaxy()-inaltime,getmaxx(),getmaxy()); setcolor(LIGHTCYAN); line(0,getmaxy()-inaltime,getmaxx(),getmaxy()-inaltime); line(0,getmaxy()-inaltime,0,getmaxy()); setcolor(MAGENTA); line(getmaxx(),getmaxy()-inaltime,getmaxx(),getmaxy()); line(0,getmaxy(),getmaxx(),getmaxy()); setcolor(culoare); // afişarea şirului: outtextxy(10,getmaxy()-inaltime+3,sir); end

// Procedura de afişare de informaţii referitoare la // secţiunea curentă, în zona de stare a ecranului, // în timpul editării sectiunilor corpului:

procedure AfisDateSectCurenta() begin int cc; char * sir; char sir1[10], sir2[10], sir3[10], sir4[10], sir5[10]; // orice scriere grafică va trebui să ascundă mouse-ul: MouseHide(); cc = getcolor(); // conversii numere întregi la şiruri de caractere: itoa(SectCurenta,sir1,10); itoa(CotaCurenta,sir2,10); itoa(MSect[SectCurenta].NrLaturi,sir3,10); itoa(CotaViitoare,sir4,10); // conversie nr. real la şir de caractere: gcvt(coef,2,sir5); // alocare memorie pentru şirul cuprinzând toate informaţiile: sir = (char * )malloc(100); // compunerea şirurilor mici în şirul mare: strcpy(sir,"Sectiune:"); strcat(sir,sir1); strcat(sir," Z:"); strcat(sir,sir2); strcat(sir," Z viitor:"); strcat(sir,sir4); strcat(sir," Numar laturi:"); strcat(sir,sir3); strcat(sir," Coef. redim.:"); strcat(sir,sir5); // afişarea şirului AfisJos(BLUE,sir); // eliberarea memoriei ocupate free(sir); setcolor(cc);

Page 20: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

20

MouseShow(); // reafişarea cursorului de mouse end

// o procedură similară pentru zona de stare din // timpul vizualizării şi rotirii corpului

procedure AfisDateCorp() begin int cc; char * sir, sir1[6]; MouseHide(); cc = getcolor(); // alocarea de memorie pt. sir şi formarea acestuia: sir = (char *) malloc(50); itoa(Unghi,sir1,10); strcpy(sir,"Unghi = "); strcat(sir,sir1); strcat(sir," Escape termina programul..."); // afişarea şirului: AfisJos(BROWN,sir); setcolor(cc); free(sir); // eliberarea memoriei MouseShow(); end

// primul meniu, având 10 comenzi, după cum urmează: TMeniu Unu = {11,{" ","Linie","Drept.","Copie","Mutare", "Coef. -","Coef. +","Redim", "Z -","Z +","Alege Z","Viz. 3D","" } }; // constante simbolice asociate comenzilor din meniu: const cmLinie=1; const cmDreptunghi=2; const cmCopie=3; const cmMutare=4; const cmCoefM=5; const cmCoefP=6; const cmRedim=7; const cmPrevZ=8; const cmNextZ=9; const cmChangeZ=10; const cmView=11; // al doilea meniu, de vizualizare, cu 11 comenzi TMeniu Doi = {11,{" ","Unghi -","Unghi +","OX -","OX +", "OY -","OY +","OZ -","OZ +","Desen", "Salvez","Incarc" } }; // constantele simbolice asociate acestuia const cmUnghiM=1; const cmUnghiP=2; const cmOXM=3; const cmOXP=4; const cmOYM=5; const cmOYP=6; const cmOZM=7; const cmOZP=8; const cmDesen=9; const cmSave=10; const cmLoad=11; // desenarea unui buton din meniu procedure Buton(int x1, int y1, int x2, int y2, char * nume, int activ) begin setfillstyle(SOLID_FILL,LIGHTGRAY); bar(x1,y1,x2,y2); if (activ) then begin setcolor(BLUE); outtextxy(x1 + (x2-x1) / 2 + 2, y1 + (y2-y1) / 2 + 2, nume); setcolor(MAGENTA); rectangle(x1,y1,x2,y2); end else begin setcolor(MAGENTA); outtextxy(x1 + (x2-x1) / 2, y1 + (y2-y1) / 2, nume); line(x2,y1,x2,y2); line(x1,y2,x2,y2); setcolor(WHITE);

Page 21: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

21

line(x1,y1,x1,y2); line(x1,y1,x2,y1); end end // afisarea unui meniu M, cu numărul NrMeniu procedure Meniu(TMeniu M, int NrMeniu, int tip) begin int i; MouseHide(); // afişarea stării în zona de jos a ecranului, // în funcţie de numărul meniului if (NrMeniu==1) then AfisDateSectCurenta(); else AfisDateCorp(); // se calculează lăţimea unei comenzi din meniu int lat = getmaxx() / M.NrCom; settextjustify(CENTER_TEXT, CENTER_TEXT); // pentru fiecare comandă i se desenează un dreptunghi, // care este alb pt. comanda curentă, în rest galben // şi în care se scrie numele comenzii M.NumeCom[i] if (tip == 2) then for (i = 1; i <= M.NrCom; i++) Buton(lat*(i-1),0,lat*i-2, inaltime,M.NumeCom[i],i==Comanda); else Buton(lat*(Comanda-1),0,lat*Comanda-2, inaltime,M.NumeCom[Comanda],tip); MouseShow(); end // când se trece la o nouă secţiune, // aceasta trebuie să fie iniţializată procedure InitSectCurenta() begin MouseHide(); MSect[SectCurenta].NrLaturi = 0; // nu are nici o latură Z[SectCurenta] = CotaCurenta; // se stabileşte cota sa clearviewport(); // se şterge ecranul, având fosta secţiune Meniu(Unu,1,2); // se afişează primul meniu MouseShow(); end // secţiunea curentă trebuie actualizată la fiecare trasare // a unei noi figuri între (px,py) şi (ux,uy), fie că aceasta // este dată fie de o linie, fie de 4 linii, în cazul unui dreptunghi procedure ActualizeazaSectCurenta(int px, int py, int ux, int uy) begin int N; // variabilă locală pt. uşurinţa scrierii // actualizarea se face în funcţie de comanda de desenare switch (Comanda) begin case cmLinie: begin // numărul de laturi din secţiunea // curentă creşte cu o unitate MSect[SectCurenta].NrLaturi++; N = MSect[SectCurenta].NrLaturi; // se adaugă această latură MSect[SectCurenta].Latura[N].p1.x = px; MSect[SectCurenta].Latura[N].p1.y = py; MSect[SectCurenta].Latura[N].p2.x = ux; MSect[SectCurenta].Latura[N].p2.y = uy; end break; // linie case cmDreptunghi: begin N = MSect[SectCurenta].NrLaturi;

Page 22: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

22

// se adaugă, pe rând, toate cele patru // laturi care sunt formate de dreptunghi MSect[SectCurenta].Latura[N+1].p1.x=px; MSect[SectCurenta].Latura[N+1].p1.y=py; MSect[SectCurenta].Latura[N+1].p2.x=ux; MSect[SectCurenta].Latura[N+1].p2.y=py; MSect[SectCurenta].Latura[N+2].p1.x=ux; MSect[SectCurenta].Latura[N+2].p1.y=py; MSect[SectCurenta].Latura[N+2].p2.x=ux; MSect[SectCurenta].Latura[N+2].p2.y=uy; MSect[SectCurenta].Latura[N+3].p1.x=ux; MSect[SectCurenta].Latura[N+3].p1.y=uy; MSect[SectCurenta].Latura[N+3].p2.x=px; MSect[SectCurenta].Latura[N+3].p2.y=uy; MSect[SectCurenta].Latura[N+4].p1.x=px; MSect[SectCurenta].Latura[N+4].p1.y=uy; MSect[SectCurenta].Latura[N+4].p2.x=px; MSect[SectCurenta].Latura[N+4].p2.y=py; // fireşte, numărul laturilor creste cu 4 MSect[SectCurenta].NrLaturi += 4; end break; // dreptunghi = 4 linii end; // switch AfisDateSectCurenta(); // se reafişează datele despre secţiune end // la apăsarea butonului 2 se poate obţine un text ajutător, // citit dintr-un fişier text .HLP // procedura se apelează în funcţie de numărul meniului şi // poziţia comenzii din meniul respectiv procedure Help(int Pozitie, int NrMeniu) begin // textul se va afişa într-o zonă dreptunghiulară, // din mijlocul ecranului, de lăţime 2*lx şi înălţime 2*ly const lx=190; const ly=80; char sir[1]; // în şir avem 1 sau 2, nr. meniului char sirul[12]; // aici ţinem numele fişierului întreg int cc; // culoarea curentă, care va fi salvată FILE * F; // fişierul F de unde se va citi char rind[80]; // rândul citit e un şir de maxim 80 caractere int p; // poziţia în fişier, a textului ce va fi afişat int py; // py = poziţia pe verticală, de afişare int b,x,y; // buton şi coordonate mouse unsigned int Size; // mărimea imaginii de sub "help" şi void * Imag; // zona de memorie unde va fi păstrată MouseHide(); cc = getcolor(); itoa(NrMeniu,sir,10); // determină numele fişierului de "help" strcpy(sirul,"MY3D"); strcat(sirul,sir); strcat(sirul,".HLP"); // se încearcă deschiderea fişierului pentru citire în modul text if ((F=fopen(sirul, "rt")) != NULL) begin // dacă operaţia reuşeşte p = 0; // se caută textul, sărind peste un număr de Pozitie şiruri ### while ((not feof(F)) and (p < Pozitie)) begin fgets(rind,80,F); if (!strcmp(rind,"###\n")) then p++; end // când se gaseşte...

Page 23: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

23

if (p==Pozitie) then begin // se determină mărimea zonei ecran ce va fi salvată // şi se salvează în Imag Size = imagesize(Xmij-lx,Ymij-ly,Xmij+lx,Ymij+ly); Imag = malloc(Size); getimage(Xmij-lx, Ymij-ly,Xmij+lx,Ymij+ly,Imag); // se desenează dreptunghiul unde se va afişa textul setfillstyle(SOLID_FILL, LIGHTCYAN); bar(Xmij-lx,Ymij-ly,Xmij+lx,Ymij+ly); py = Ymij-ly+3; // se iniţializează pozitia pe verticală // afişarea va fi făcută centrat settextjustify(CENTER_TEXT, TOP_TEXT); strcpy(rind,""); // cât timp nu s-a ajuns la sfârşitul fişierului // sau la vreun şir ### while ((not feof(F)) and (strcmp(rind,"###\n"))) begin // se citeşte un şir rind de maxim 80 caractere fgets(rind,80,F); // primul şir se afişează cu albastru, if (py == Ymij-ly+3) then setcolor(BLUE); // ... iar restul cu roşu else setcolor(RED); // dacă şirul nu este ###, // atunci i se pune caracterul '\0' // la sfârşit şi se afişează if (strcmp(rind,"###\n")) then begin rind[strlen(rind)-1]='\0'; outtextxy(Xmij,py,rind); end py += 10; // se coboară cu o linie end // se închide fişierul fclose(F); MouseShow(); // o pauză necesară eliberării butonului din dreapta // al mouse-ului, care a apelat această procedură delay(300); // se aşteaptă apăsarea unei taste sau a unui buton do { MouseData(&b,&x,&y); } while (! ((kbhit()) or (b != 0))); // dacă s-a acţionat Esc, trebuie aşteptată eliberarea // tastei, pentru a nu se ajunge în situaţia de terminare // a programului, datorită funcţiei SeApasaEscape() if (kbhit()) then if (getch()==27) then delay(300); MouseHide(); // se restaurează imaginea salvată anterior putimage(Xmij-lx,Ymij-ly,Imag,COPY_PUT); // şi se eliberează zona de memorie folosită free(Imag); end end // se revine la culoarea dinaintea procedurii setcolor(cc); MouseShow(); end // se stabileşte comanda curentă din meniul M, cu numărul NrMeniu

Page 24: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

24

procedure StabilesteComanda(TMeniu M, int NrMeniu) begin int b,x,y; // variabile pt. test mouse int i; // variabila de căutare a comenzii int lat = getmaxx() / M.NrCom; // lăţimea unei comenzi din meniu MouseData(&b,&x,&y); // se citesc datele de mouse if ((b==1) or (b==2)) then // dacă s-a acţionat unul din butoane for (i=1; i <= M.NrCom; i++) // atunci se caută prima comandă în dreptul căreia este mouse-ul if (Apartine(x,y,lat*(i-1),0,lat*i,inaltime)) then if (b==1) then // dacă se acţionase butonul din stînga, // atunci aceasta este comanda curentă Comanda = i; else // altfel, la butonul din dreapta // se apelează ajutor la respectiva comandă Help(i,NrMeniu); // dacă butonul din dreapta s-a acţionat in înteriorul // zonei de lucru, atunci se obţine un alt text ajutător if ((b==2) and (Apartine(x,y,0,inaltime+1,getmaxx(),getmaxy()))) then Help(M.NrCom+1,NrMeniu); // la fel, un text special la apăsarea tastei F1, având codurile (0,59) if (kbhit()) then if (getch()==0) then if (getch()==59) then Help(Doi.NrCom+2,2); end // această procedură desenează o linie între două puncte // folosind mouse-ul; tehnica de desenare este următoarea: // se fixează un punct (în interiorul zonei de desenare permise), // după care, în modul de scriere Xor, se trasează (de două ori) // o linie între primul punct fixat şi punctul curent, până ne // hotărâm la punctul final procedure DeseneazaLinie() begin int b,px,py,x,y; // (px,py) = primul punct, (x,y) = al doilea MouseData(&b,&px,&py); // Unde este mouse-ul ? // S-a acţionat butonul stâng şi suntem în zona de desenare ? if ((Apartine(px,py,0,inaltime+1,getmaxx(), getmaxy()-inaltime-1)) and (b==1)) then begin // Dacă da, alegem acest mod de scriere şi ascundem mouse-ul: setwritemode(XOR_PUT); MouseHide(); do { // Care sunt noile coordonate ale mouse-ului ? MouseData(&b,&x,&y); // Sunt ele valide ? Dacă nu se forţează la limite. if (y<inaltime+1) then begin // limita de sus (sub meniu) y = inaltime+1; MouseMove(x,y); // acolo se mută mouse-ul end if (y>getmaxy()-inaltime-1) then begin // limita de jos (peste zona de stare) y = getmaxy()-inaltime-1; MouseMove(x,y); end line(px,py,x,y); // Se trasează linia, apoi... // ...se arată mouse-ul şi linia, puţin timp,

Page 25: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

25

MouseShow(); delay(50); MouseHide(); line(px,py,x,y); // ... după care se şterge linia // {ştergerea este datorată afişării cu Xor = 1 } while (b != 0); // Totul se opreşte când dăm drumul la butonul de mouse. line(x,y,px,py); // Se trasează linia finală... setwritemode(COPY_PUT); // ... şi se revine la modul normal... MouseShow(); // Se reafişează cursorul de mouse. // În fine, se actualizează secţiunea curentă, adică // se adaugă linia trasată în rândul laturilor din // SectCurenta ActualizeazaSectCurenta(px,py,x,y); end end // O procedură similară realizează trasarea unui dreptunghi, // după aceeaşi tehnică; dreptunghiul este determinat unic // de două colţuri opuse: (px,py) şi (x,y); // la sfârşit SectCurenta se actualizează cu 4 laturi. procedure DeseneazaDreptunghi() begin int b,px,py,x,y; MouseData(&b,&px,&py); if (Apartine(px,py,0,inaltime,getmaxx(),getmaxy()))and(b==1)) then begin setwritemode(XOR_PUT); MouseHide(); do { MouseData(&b,&x,&y); if (y<inaltime+1) then begin y = inaltime+1; MouseMove(x,y); end if (y>getmaxy()-inaltime-1) then begin y = getmaxy()-inaltime-1; MouseMove(x,y); end rectangle(px,py,x,y); MouseShow(); delay(50); MouseHide(); rectangle(px,py,x,y); } while (b != 0); rectangle(x,y,px,py); setwritemode(COPY_PUT); MouseShow(); ActualizeazaSectCurenta(px,py,x,y); end end // verifică dacă punctul de coordonate (x,y) este // în apropierea mijlocului laturii L int Aproape(int x, int y, TLatura L) begin int eps=5; int xm = (L.p1.x + L.p2.x)/2; int ym = (L.p1.y + L.p2.y)/2; if ((abs(x-xm) < eps) and (abs(y-ym) < eps)) then return 1; else return 0; end // procedura de mutare în altă poziţie

Page 26: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

26

// a unei drepte din secţiunea curentă procedure MutareLinie() begin int i,gasit,ii,b,x,y; TLatura L; MouseData(&b,&x,&y); if (b==1) then begin gasit = 0; for (i = 1; (i <= MSect[SectCurenta].NrLaturi) and (not gasit); i++) if (Aproape(x,y,MSect[SectCurenta].Latura[i])) begin gasit = 1; ii = i; end if (gasit) then begin setwritemode(XOR_PUT); MouseHide(); L = MSect[SectCurenta].Latura[ii]; line(L.p1.x,L.p1.y,L.p2.x,L.p2.y); int xm = (L.p1.x + L.p2.x)/2; int ym = (L.p1.y + L.p2.y)/2; do { MouseData(&b,&x,&y); if (y < inaltime + 1) then begin y = inaltime + 1; MouseMove(x,y); end if (y > getmaxy() - inaltime - 1) then begin y = getmaxy() - inaltime - 1; MouseMove(x,y); end line(L.p1.x+x-xm,L.p1.y+y-ym, L.p2.x+x-xm,L.p2.y+y-ym); MouseShow(); delay(50); MouseHide(); line(L.p1.x+x-xm,L.p1.y+y-ym, L.p2.x+x-xm,L.p2.y+y-ym); } while (b == 1); line(L.p1.x+x-xm,L.p1.y+y-ym, L.p2.x+x-xm,L.p2.y+y-ym); setwritemode(COPY_PUT); L.p1.x += x-xm; L.p1.y += y-ym; L.p2.x += x-xm; L.p2.y += y-ym; MSect[SectCurenta].Latura[ii] = L; MouseShow(); end end end // Această procedură copiază conţinutul fostei secţiuni în secţiunea // curentă, renunţând la conţinutul acesteia din urmă. procedure CopiazaDinSectVeche() begin int i; int SC = SectCurenta; MouseHide(); Beep(); // se avertizeaza sonor operatia // În cazul în care se doreşte acest lucru pentru prima // secţiune, atunci se şterge pur şi simplu secţiunea if (SectCurenta == 1) then begin MSect[SectCurenta].NrLaturi = 0; clearviewport(); Meniu(Unu,1,2); end else // altfel se face copierea din MSect[SC-1] în MSect[SC] // SC = SectCurenta begin

Page 27: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

27

// fireşte se şterge şi ecranul, la început... setviewport(0,inaltime+1,getmaxx(), getmaxy()-inaltime-1,1); clearviewport(); setwritemode(XOR_PUT); MSect[SC].NrLaturi = MSect[SC-1].NrLaturi; for (i=1; i <= MSect[SC].NrLaturi; i++) begin // copiere latură cu latură MSect[SC].Latura[i].p1.x= ((MSect[SC-1].Latura[i]).p1).x; MSect[SC].Latura[i].p1.y= ((MSect[SC-1].Latura[i]).p1).y; MSect[SC].Latura[i].p2.x = ((MSect[SC-1].Latura[i]).p2).x; MSect[SC].Latura[i].p2.y= ((MSect[SC-1].Latura[i]).p2).y; // .. şi se desenează fiecare nouă latură line(MSect[SC].Latura[i].p1.x, MSect[SC].Latura[i].p1.y-inaltime-1, MSect[SC].Latura[i].p2.x, MSect[SC].Latura[i].p2.y-inaltime-1); end setwritemode(COPY_PUT); setviewport(0,0,getmaxx(),getmaxy(),1); end MouseShow(); end // copie // În funcţie de coef, laturile din secţiunea curentă pot fi // redimensionate. Are loc o transformare a coordonatelor acestor // laturi, raportate la centrul ecranului. procedure RedimensioneazaSectCurenta() begin int i,cc; int xr1,xr2,yr1,yr2; // variabile locale auxiliare int SC = SectCurenta; MouseHide(); cc = getcolor(); // se selectează zona de ecran cuprinsă între meniu şi // zona de afişare a datelor, din partea de jos a ecranului setviewport(0,inaltime+1,getmaxx(),getmaxy()- inaltime-1,1); clearviewport(); setcolor(WHITE); for (i=1; i <= MSect[SC].NrLaturi; i++) begin // se determină coordonatele reale ale punctelor // în funcţie de coordonatele ecran ale lor: xr1 = XReal(MSect[SC].Latura[i].p1.x); yr1 = YReal(MSect[SC].Latura[i].p1.y); xr2 = XReal(MSect[SC].Latura[i].p2.x); yr2 = YReal(MSect[SC].Latura[i].p2.y); // se recalculează coordonatele reale: xr1 = Round(xr1*coef); xr2 = Round(xr2*coef); yr1 = Round(yr1*coef); yr2 = Round(yr2*coef); // se determină noile coordonate ecran, // în funcţie de noile coordonate reale: MSect[SC].Latura[i].p1.x = xr1 + Xmij; MSect[SC].Latura[i].p1.y = Ymij - yr1; MSect[SC].Latura[i].p2.x = xr2 + Xmij; MSect[SC].Latura[i].p2.y = Ymij - yr2; // se desenează noua latură, redimensionată:

Page 28: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

28

line(MSect[SC].Latura[i].p1.x, MSect[SC].Latura[i].p1.y, MSect[SC].Latura[i].p2.x, MSect[SC].Latura[i].p2.y); end // se revine la ecranul întreg: setviewport(0,0,getmaxx(),getmaxy(),1); setcolor(cc); MouseShow(); end // Această procedură salvează corpul editat într-un fişier, // cu numele NumeFis. Apelul în program se face cu CORP.3D. // Propunem cititorului îmbunătăţirea programului pentru a permite // crearea de fişiere cu nume date de utilizator. procedure SalveazaCorpul(char * NumeFis) begin FILE * F; // fisierul binar int i; // i = punct curent if ((F=fopen(NumeFis, "wb")) != NULL) begin // Se deschide, pentru scriere binară, fişierul F, // se scrie în el numărul de puncte şi numărul de linii // ale corpului... fwrite(&NrPuncte, sizeof(NrPuncte), 1, F); fwrite(&NrLinii, sizeof(NrLinii), 1, F); // ...apoi se salvează punctele... for (i=1; i <= NrPuncte+4; i++) fwrite(&MPuncte[i], sizeof(MPuncte[i]), 1, F); // ...şi liniile for (i=1; i <= NrLinii+3; i++) fwrite(&MLinii[i], sizeof(MLinii[i]), 1, F); // se închide fişierul. fclose(F); end end // Inversa procedurii de mai înainte este procedura... procedure IncarcaCorpul(char * NumeFis) begin FILE * F; int i, Lat; if ((F=fopen(NumeFis, "rb")) != NULL) begin // ... care citeşte numărul de puncte... fread(&NrPuncte, sizeof(NrPuncte), 1, F); // ... si de linii fread(&NrLinii, sizeof(NrLinii), 1, F); // ... apoi toate punctele şi toate liniile for (i=1; i <= NrPuncte+4; i++) fread(&MPuncte[i], sizeof(MPuncte[i]), 1, F); for (i=1; i <= NrLinii+3; i++) fread(&MLinii[i], sizeof(MLinii[i]), 1, F); fclose(F); end end // Cel de al doilea meniu permite vizualizarea, dar şi rotirea // corpului creat în prima fază. Rotirea se face cu un unghi // exprimat în grade (număr întreg, negativ sau pozitiv). // Există trei feluri de rotire, după cele trei axe: OX, OY şi OZ. // De fapt, se realizează rotiri ale ochiului observatorului pe // o sferă centrată în (0,0,0) (care este mai aproape de corp), // sfera de rază ymax, în cele trei direcţii:

Page 29: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

29

// sus-jos (pe Y), stânga-dreapta şi faţă-spate. // Rotirea corpului după OX presupune recalcularea geometrică // a coordonatelor corpului, precum şi a punctelor care determină axele procedure RotireOX(int alfa) begin int i; float beta; // unghiul exprimat în radiani int aux1,aux2; beta = alfa*M_PI/180; for (i=1; i <= NrPuncte+4; i++) // !! begin aux1 = Round(MPuncte[i].y*cos(beta) -MPuncte[i].z*sin(beta)); aux2 = Round(MPuncte[i].y*sin(beta)+ MPuncte[i].z*cos(beta)); MPuncte[i].y = aux1; MPuncte[i].z = aux2; end end // La fel şi în cazul celorlalte două rotiri: după OY... procedure RotireOY(int alfa) begin int i; float beta; int aux1,aux2; beta = alfa*M_PI/180; for (i=1; i <= NrPuncte+4; i++) // !! begin aux1 = Round(MPuncte[i].x*cos(beta) -MPuncte[i].z*sin(beta)); aux2 = Round(MPuncte[i].x*sin(beta) +MPuncte[i].z*cos(beta)); MPuncte[i].x = aux1; MPuncte[i].z = aux2; end end // ... şi după OZ. procedure RotireOZ(int alfa) begin int i; float beta; int aux1,aux2; beta = alfa*M_PI/180; for (i=1; i <= NrPuncte+4; i++) // !! begin aux1 = Round(MPuncte[i].x*cos(beta)- MPuncte[i].y*sin(beta)); aux2 = Round(MPuncte[i].x*sin(beta)+ MPuncte[i].y*cos(beta)); MPuncte[i].x = aux1; MPuncte[i].y = aux2; end end // Corpul memorat prin puncte cu trei cooordonate şi linii // ce unesc aceste puncte, două câte două, trebuie proiectat // pe ecran, în planul 2D, pentru a putea fi vizualizat. // Vectorul MPro conţine toate coordonatele proiecţiilor // tuturor celor NrPuncte puncte. Se ţine cont de distanţa // ymax de unde priveşte observatorul. procedure ProiecteazaCorpul() // Obţin în MPro imaginea pe ecran a lui MPct. begin int i, yr; double xr, aa; // Mare bătaie de cap cu împărţirile astea ! ... // Pentru a nu avea probleme cu erorile de calcul, // se foloseşte tipul double şi conversia la el a întregilor... for (i=1; i <= NrPuncte+4; i++) // se continuă cu cele trei axe OX, OY şi OZ begin

Page 30: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

30

yr = MPuncte[i].y; if (yr > ymax) then yr = ymax; if (yr < -ymax) then yr = -ymax; xr = double(MPuncte[i].x); aa = double(double(ymax-yr)/double(ymax)); MPro[i].a=Round(double(double(xr*aa) +double(Xmij))); MPro[i].b=getmaxy()- Round((double(MPuncte[i].z)*aa) +double(Ymij)); delay(10); end end // După ce a fost proiectat, corpul păstrează legăturile date de MLinii // deoarece dacă două puncte MPuncte[k] şi MPuncte[l] sunt unite, // atunci şi proiecţiile sale MPro[k] şi MPro[l] sunt unite. // Deci vom face, pentru desenare, o parcurgere a mulţimii liniilor. procedure DeseneazaCorpul() // Desenez imaginea din MPro. begin int i; MouseHide(); // Se stabileşte zona permisă de desenare, pentru a nu avea scrieri // în meniu sau în zona de jos. setviewport(0,inaltime+1,getmaxx(), getmaxy() -inaltime-1, 1); clearviewport(); setcolor(LIGHTGREEN); // Se desenează toate liniile corpului proiectat... for (i = 1; i <= NrLinii; i++) line(MPro[MLinii[i].p].a, MPro[MLinii[i].p].b, MPro[MLinii[i].q].a, MPro[MLinii[i].q].b); // ... eventual şi axele de coordonate 3D. if (Axe3D) then begin setcolor(YELLOW); // în altă culoare for (i = NrLinii+1; i <= NrLinii+3; i++) begin line(MPro[MLinii[i].p].a, MPro[MLinii[i].p].b, MPro[MLinii[i].q].a, MPro[MLinii[i].q].b); // în capetele lor se afişează X, Y şi Z if (i == NrLinii+1) then outtextxy(MPro[MLinii[i].q].a-3, MPro[MLinii[i].q].b-3,"X"); if (i == NrLinii+2) then outtextxy(MPro[MLinii[i].q].a-3, MPro[MLinii[i].q].b-3,"Y"); if (i == NrLinii+3) then outtextxy(MPro[MLinii[i].q].a-3, MPro[MLinii[i].q].b-3,"Z"); end end; // Se revine la întreg ecranul grafic. setviewport(0,0,getmaxx(),getmaxy(), 1); MouseShow(); setcolor(WHITE); end // Această procedură desenează "vag" ce a fost creat

Page 31: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

31

// în secţiunea anterioară, indiferent dacă are o cotă diferită sau nu. procedure VizualizeazaSectAnterioara() begin int cc = getcolor(); int i, SC = SectCurenta-1; if (SC < 1) then SC = 1; setcolor(MAGENTA); MouseHide(); for (i=1; i <= MSect[SC].NrLaturi; i++) begin line(MSect[SC].Latura[i].p1.x, MSect[SC].Latura[i].p1.y-inaltime-1, MSect[SC].Latura[i].p2.x, MSect[SC].Latura[i].p2.y-inaltime-1); end MouseShow(); setcolor(cc); end // Următoarele două proceduri sunt necesare unor comenzi din // ambele meniuri, comenzi care au o acţiune imediată şi nu // trebuie să lase valoarea variabilei Comanda setată pe ele. // De asemenea, aceste proceduri actualizează zona de stare. procedure Revino1() // valabilă pt. primul meniu begin AfisDateSectCurenta(); Meniu(Unu,1,1); delay(100); // temporizare necesară eliberării butonului. Meniu(Unu,1,0); Comanda = cmNimic; end procedure Revino2() // valabilă pentru cel de al doilea meniu. begin AfisDateCorp(); Meniu(Doi,2,1); delay(100); Meniu(Doi,2,0); Comanda = cmNimic; end // Urmează o procedură care execută comanda curentă, în funcţie // de meniul din care a fost selectată. procedure ExecutaComanda(int NrMeniu) begin if (NrMeniu == 1) then // primul meniu: editare switch (Comanda) begin case cmLinie: DeseneazaLinie(); break; case cmDreptunghi: DeseneazaDreptunghi(); break; case cmCopie: begin CopiazaDinSectVeche(); Revino1(); end break; case cmMutare: MutareLinie(); break; case cmCoefM: begin // scăderea coeficientului // de redimensionare coef = coef - 0.1; Revino1(); end break; case cmCoefP: begin // creşterea sa

Page 32: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

32

coef = coef + 0.5; Revino1(); end break; case cmRedim: begin // redimensionare RedimensioneazaSectCurenta(); Revino1(); end break; case cmNextZ: begin // creşterea valorii Z // pentru secţiunea următoare. CotaViitoare += 10; Revino1(); end break; case cmPrevZ: begin // scăderea lui Z viitor CotaViitoare-- ; Revino1(); end break; case cmChangeZ: begin // alegerea unei noi secţiuni... SectCurenta++ ; CotaCurenta = CotaViitoare; // ...care va fi iniţializată InitSectCurenta(); Beep(); Revino1(); // se desenează "şters" secţiunea anterioară VizualizeazaSectAnterioara(); end break; end else // meniul al doilea: vizualizare begin switch (Comanda) begin // scăderea şi creşterea unghiului folosit la rotiri case cmUnghiM: Unghi -= 5; Revino2(); break; case cmUnghiP: Unghi += 10; Revino2(); break; // rotiri după OX case cmOXM : RotireOX(-Unghi); break; case cmOXP : RotireOX(Unghi); break; // ... după OY case cmOYM : RotireOY(-Unghi); break; case cmOYP : RotireOY(Unghi); break; // ... şi după OZ case cmOZM : RotireOZ(-Unghi); break; case cmOZP : RotireOZ(Unghi); break; // salvarea corpului în fişierul CORP.3D case cmSave : begin SalveazaCorpul("CORP.3D"); Comanda = cmNimic; end break; // restaurarea corpului din acelaşi fişier case cmLoad: IncarcaCorpul("CORP.3D"); break; end; // Dacă s-a dat vreo comandă ce afectează coordonatele // corpului, atunci acesta se reproiectează pe ecran. if ((Comanda == cmOXM) or (Comanda == cmOXP) or (Comanda == cmOYM) or (Comanda == cmOYP) or (Comanda == cmOZM) or (Comanda == cmOZP) or (Comanda == cmLoad)) then begin ProiecteazaCorpul(); DeseneazaCorpul(); Comanda = cmNimic; end end end

Page 33: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

33

// Prima fază a programului: editarea corpului. procedure EditareDesen() begin // iniţializări SectCurenta=1; CotaCurenta=-50; CotaViitoare=CotaCurenta; coef=1; AfisDateSectCurenta(); InitSectCurenta(); // Se execută comenzi, până se apasă Esc sau se trece // în cea de a doua fază, cu comanda de Vizualizare Comanda=cmNimic; ComandaVeche=cmNimic; do { StabilesteComanda(Unu,1); if ((Comanda != ComandaVeche) and (Comanda != cmNimic)) then begin Meniu(Unu,1,2); ComandaVeche=Comanda; end ExecutaComanda(1); } while (!((Comanda == cmView) or (SeApasaEscape()))); MouseHide(); clearviewport(); MouseShow(); end // Dar, înainte de a trece în cea de a doua fază, se apelează această procedură. // Ea transformă MSect în MPuncte şi MLinii, creând puncte // corespunzătoare punctelor din fiecare secţiune şi unindu-le între ele, // apoi unind punctele din secţiuni consecutive, două câte două, // corespunzător. procedure TransformaSectiuni() begin int Sec,Lat, PuncteUnite; // Este necesar un vector care să memoreze numărul de puncte unite // din cadrul fiecărei secţiuni în parte. int NrPcteUnite[NrMaxSectiuni+1]; NrLinii = 0; NrPuncte = 0; PuncteUnite = 0; NrPcteUnite[0] = 0; for (Sec=1; Sec <= SectCurenta; Sec++) begin NrPcteUnite[Sec]=0; for (Lat=1; Lat <= MSect[Sec].NrLaturi; Lat++) begin NrLinii++; PuncteUnite++; NrPcteUnite[Sec]++; // Conversie ... MPuncte[PuncteUnite].x = XReal(MSect[Sec].Latura[Lat].p1.x); MPuncte[PuncteUnite].y = YReal(MSect[Sec].Latura[Lat].p1.y); MPuncte[PuncteUnite].z = Z[Sec]; MLinii[NrLinii].p = PuncteUnite; MLinii[NrLinii].q = PuncteUnite+1; // nu e vecină cu latura următoare if ((Lat == MSect[Sec].NrLaturi) or ( (Lat+1 <= MSect[Sec].NrLaturi) and (!( ( MSect[Sec].Latura[Lat].p2.x == MSect[Sec].Latura[Lat+1].p1.x ) and ( MSect[Sec].Latura[Lat].p2.y == MSect[Sec].Latura[Lat+1].p1.y ) ) ) )) then begin

Page 34: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

34

PuncteUnite++; NrPcteUnite[Sec]++; MPuncte[PuncteUnite].x = XReal(MSect[Sec].Latura[Lat].p2.x); MPuncte[PuncteUnite].y = YReal(MSect[Sec].Latura[Lat].p2.y); MPuncte[PuncteUnite].z = Z[Sec]; end end NrPuncte += NrPcteUnite[Sec]; end // for Sec PuncteUnite = 0; for (Sec=2; Sec <= SectCurenta; Sec++) begin PuncteUnite += NrPcteUnite[Sec-1]; if (Z[Sec] != Z[Sec-1]) then for (Lat=1; Lat<=min(NrPcteUnite[Sec-1], NrPcteUnite[Sec]);Lat++) begin NrLinii++; MLinii[NrLinii].p = PuncteUnite - NrPcteUnite[Sec-1]+Lat; MLinii[NrLinii].q = PuncteUnite + Lat; end // for Lat end // for Sec // axele MPuncte[NrPuncte+1].x = 0; MPuncte[NrPuncte+1].y = 0; MPuncte[NrPuncte+1].z = 0; MPuncte[NrPuncte+2].x = 50; MPuncte[NrPuncte+2].y = 0; MPuncte[NrPuncte+2].z = 0; MPuncte[NrPuncte+3].x = 0; MPuncte[NrPuncte+3].y = 50; MPuncte[NrPuncte+3].z = 0; MPuncte[NrPuncte+4].x = 0; MPuncte[NrPuncte+4].y = 0; MPuncte[NrPuncte+4].z = 50; MLinii[NrLinii+1].p = NrPuncte+1; MLinii[NrLinii+1].q = NrPuncte+2; MLinii[NrLinii+2].p = NrPuncte+1; MLinii[NrLinii+2].q = NrPuncte+3; MLinii[NrLinii+3].p = NrPuncte+1; MLinii[NrLinii+3].q = NrPuncte+4; end // transform procedure VizualizeazaCorp() begin // o primă vizualizare a corpului Unghi = 0; Comanda = cmOXP; ExecutaComanda(2); // iniţializări... Unghi = 30; AfisDateCorp(); Meniu(Doi,2,2); // Se execută fiecare comandă aleasă din meniu. Comanda=cmNimic; ComandaVeche=cmNimic;

Page 35: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

35

do { StabilesteComanda(Doi,2); if ((Comanda != ComandaVeche) and (Comanda != cmNimic)) then begin Meniu(Doi,2,2); ComandaVeche=Comanda; end ExecutaComanda(2); } while (!( (Comanda == cmDesen) or (SeApasaEscape() ) )); // Se poate reveni în prima fază cu comanda Desen sau se // poate termina programul cu tasta Escape. MouseHide(); clearviewport(); MouseShow(); end procedure main() // În sfârşit, funcţia main(), adică programul principal... begin int b,x,y; OpenGraph(); setbkcolor(DARKGRAY); Xmij = getmaxx() / 2; Ymij = getmaxy() / 2; MouseInit(); Help(Doi.NrCom+1,2); // un text de prezentare /* F|R| COMENTARII ... */ do { EditareDesen(); TransformaSectiuni(); delay(200); Axe3D = 1; VizualizeazaCorp(); } while (! SeApasaEscape()); closegraph(); end

Page 36: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

36

Page 37: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

37

Capitolul 2. Animaţie profesională În diferite cărţi de grafică sînt prezentate tehnici de bază de animaţie, de cele mai multe ori menţionîndu-se următoarele trei: • redesenarea în vechea poziţie cu modul de scriere xor, care determină ştergerea desenului şi

scrierea în noua poziţie; (se foloseşte setwritemode); • alternarea paginilor video; (se folosesc setvisualpage şi setactivepage); • prelucrarea directă a diferite zone dreptunghiulare de ecran (se folosesc getimage şi putimage). Este evident pentru oricine că, de cele mai multe ori, aceste tehnici nu dau rezultate formidabile, nici chiar dacă se combină între ele. Ele prezintă unele dezavantaje evidente: prima duce la clipirea imaginii pe ecran, a doua la o redesenare care încetineşte ritmul animaţiei, iar a treia la o alocare de memorie dinamică destul de mare ca dimensiuni, în heap. Programele profesionale de animaţie, cum este Autodesk Animator, prezintă un moment de animaţie prin suprapunerea mai multor cadre (frame-uri), unul după altul. Acestea se citesc de pe disc, dintr-un fişier cu extensia FLI (sau, mai nou, FLC), care se numeşte “flic” şi care are memorat paleta de culori folosite, precum şi primul cadru din animaţie, apoi, pentru celelalte, diferenţele faţă de cadrul precedent. În cele ce urmează ne propunem să prezentăm structura fişierelor FLI folosite de Autodesk Animator, precum şi un program exemplu de vizualizare a unor astfel de flic-uri, scris în Borland C.

2.1. Fişierele de animaţie FLI Detaliile unui fişier FLI (un “flic”, fişiere de animaţie folosite în Autodesk Animator, 3D Studio) sînt relativ complexe, dar ideea de bază este simplă: nu se memorează părţile unui cadru (frame) care sînt aceleaşi ca şi în ultimul cadru. Nu pentru că acest lucru economiseşte spaţiu, ci pentru că este mult mai rapid procesul de animaţie, efectuat prin suprapunerea unor cadre. Este mult mai rapid să laşi un pixel în pace, decît să-l setezi ! Iată, de exemplu, două cadre succesive (care nu diferă prea mult între ele) dintr-un fişier FLI:

Două cadre succesive dintr-un flic

Un fişier FLI are un cap (header) de 128 de bytes, urmat de o secvenţă de cadre. Primul cadru este compresat utilizînd o schemă de compresie de tip “bytewise run”. Cadrele următoare sînt memorate prin diferenţa faţă de cadrul precedent. (Ocazional, primul cadru şi / sau celelalte sînt necompresate.). Există, de asemenea, la sfîrşitul unui FLI, un extra-cadru ce conţine diferenţa între ultimul cadru şi primul cadru. Header-ul unui fişier FLI este:

Page 38: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

38

byte offset

mărime

nume semnificaţie

0 4 size Lungimea fişierului, pentru programele care doresc să citească flic-ul cu totul, dacă este posibil

4 2 magic Setat la valoarea AF11 (în hexa). Vă rugăm să folosiţi aici altă valoare, dacă schimbaţi formatul (chiar şi pentru o altă rezoluţie), astfel încît Autodesk Animator să nu se blocheze, încercând să citească flic-ul.

6 2 frames Numărul de cadre din FLI. Fişierele FLI au un conţinut de maxim 4000 cadre.

8 2 width Lăţimea ecranului (320). 10 2 height Înălţimea ecranului (200). 12 2 depth Adâncimea unui pixel (8). 14 2 flags Trebuie să fie 0. 16 2 speed Numărul de momente video între cadre.. 18 4 next Setat la 0. 22 4 frit Setat la 0. 26 102 expand Toate zero -- pentru dezvoltări viitoare.

Urmează cadrele (frame-urile), fiecare din ele avînd un header propriu:

byte offset

mărime

nume semnificaţie

0 4 size Numărul de bytes în cadre. Pentru Autodesk Animator acesta este sub 64K.

4 2 magic Întotdeauna hexazecimal F1FA. 6 2 chunks Numărul de 'chunks' (“hălci”) în cadru. 8 8 expand Spaţiu pentru dezvoltări ulterioare. Toate zerouri.

După header-ul cadrului vin “grupele” (chuncks) care formează cadrul. Mai întîi vine un

chunk de culoare dacă paleta de culori s-a schimbat faţă de ultimul cadru. Apoi urmează un chunck de pixeli, dacă pixelii s-au schimbat. Dacă acest cadru este absolut identic cu precedentul, atunci nu va fi nici un chunck.

Un chunck, la rîndul său, are un header, urmat de date (culorile pixelilor). Header-ul unui chunck este:

byte offset

mărime

nume semnificaţie

0 4 size Numărul de bytes în acest chunk. 4 2 type Tipul de chunk (vezi mai jos).

În mod curent, există cinci tipuri de chunck-uri pe care le puteţi vedea într-un fişier FLI:

număr nume semnificaţie 11 FLI_COLOR Harta compresată a culorilor (paleta de culori). 12 FLI_LC Linie compresată - cea mai întîlnită formă de compresie

pentru orice cadru, mai puţin pentru primul. Descrie diferenţele de pixeli faţă de cadrul precedent.

Page 39: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

39

13 FLI_BLACK Setează întregul ecran la culoarea 0 (apare doar la primul cadru).

15 FLI_BRUN Compresie de tip “bytewise run-length” -- doar primul cadru. 16 FLI_COPY Indică 64000 bytes necompresaţi care urmează. Pentru

momentele în care compresia nu lucrează ! Schemele de compresie sînt toate orientate pe byte. Dacă datele de compresie dintr-un cadru se termină la o adresă impară, se mai inserează un pixel, astfel încît cadrele FLI_COPY vor începe întotdeauna la o adresă pară, ceea ce permite un acces video direct (DMA) mai rapid. FLI_COLOR Chunk Primul word (cuvînt) (2 bytes) este numărul de pachete (packets) din acest chunck. Acesta este urmat direct de pachete. Primul byte al unui pachet spune cîte culori trebuie să fie sărite. Următorul byte ne spune cîte culori se schimbă. Dacă acest byte este zero, el trebuie interpretat ca fiind 256. Apoi urmează 3 bytes pentru fiecare culoare în parte care se schimbă (unul pentru roşu, unul pentru verde şi unul pentru albastru). FLI_LC Chunk Acesta este cel mai comun şi, de asemenea, cel mai complex chunck. Primul word (16 biţi) este numărul de linii, începînd cu partea de sus a ecranului, care sunt identice cu cele din cadrul precedent. (De exemplu, dacă există modificări doar pe cea mai de jos linie a ecranului, vom avea aici valoarea 199). Următorul word este numărul de linii care se schimbă. Apoi urmează datele pentru schimbarea liniilor. Fiecare linie este compresată individual, printre altele aceasta determinînd simplitatea vizualizării unui FLI la o mărime redusă. Primul byte al unei linii compresate este numărul de pachete în respectiva linie. Dacă linia este neschimbată faţă de cea din ultimul cadru, acest număr este zero. Formatul unui pachet individual este: skip_count size_count data Aici, skip_count (contorul de salt) este un singur byte. Dacă mai mult de 255 pixeli trebuie să fie săriţi, trebuie ca pachetul să fie descompus în două pachete. {i size_count (contorul de mărime) este, de asemenea, un byte. Dacă acesta este pozitiv, aceasta înseamnă că mai mulţi bytes de date (data) urmează şi vor fi copiate pe ecran, iar dacă este negativ, un singur byte urmează şi el este repetat de -size_count ori. În cel mai rău caz, un cadru FLI_LC poate fi cam de 70K. Dacă el ajunge să fie de 60000 bytes sau chiar mai mult, AutoDesk Animator decide să renunţe la compresie (care este ineficientă) şi să salveze cadrul sub forma de FLI_COPY. FLI_BLACK Chunks Acest tip este foarte simplu. Nu există date asociate. Acest chunck (care “curăţă” ecranul) este generat doar pentru primul cadru în Autodesk Animator, după ce a fost utilizată comanda NEW din meniul FLIC. FLI_BRUN Chunk Aceste chunck-uri sunt foarte asemănătoare celor de tip FLI_LC, dar nu apar salturile. Ele încep imediat cu date pentru prima linie a ecranului şi continuă, linie cu linie, în jos. Primul byte conţine numărul de pachete în linia respectivă. Formatul pentru un pachet este: size_count data

Page 40: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

40

Dacă size_count este pozitiv, data constă dintr-un singur byte care se repetă de size_count ori. Dacă size_count este negativ, atunci există -size_count bytes de date, care sunt copiate pe ecran. În Autodesk Animator, dacă datele “compresate” semnalizează depăşirea a 60000 bytes, atunci cadrul este stocat sub forma unui FLI_COPY. FLI_COPY Chunk Aceste chunck-uri sunt reprezentate de 64000 bytes de date pentru citiri şi scrieri directe pe ecran.

2.2. Programul PLFLI pentru animat fişiere FLI Programul foloseşte unele definiţii date în header-ul PASCAL.H pe care l-am folosit şi în programul de grafică tridimensională. // PLFLI - Program de vizualizare (playback) a unui flic (fişier FLI) // by Bogdan Pătruţ, Bacău, 1995 #include <dos.h> #include <stdio.h> #include <string.h> // Definiţii pentru similitudini cu limbajul Pascal: #define procedure void #define begin { #define end } #define then typedef unsigned char Byte; typedef unsigned int Word; // Tipul de Chunck #define FLI_COLOR 11 #define FLI_LC 12 #define FLI_BLACK 13 #define FLI_BRUN 15 #define FLI_COPY 16 procedure SetColorRegister (Byte RegColor, Byte RedValue, Byte GreenValue, Byte BlueValue) begin union REGS reggss; reggss.h.ah=0x10; reggss.h.al=0x10; reggss.x.bx=RegColor; reggss.h.ch=GreenValue; reggss.h.cl=BlueValue; reggss.h.dh=RedValue; int86(0x10,&reggss,&reggss); end procedure SetVideoMode(Byte VideoCode) begin union REGS reggss; reggss.h.al=VideoCode; reggss.h.ah=0; int86(0x10,&reggss,&reggss);

Page 41: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

41

end procedure Plot(int x, int y, Byte c) begin Byte Pixel=c; int X=x, Y=y; // Se poate mai simplu cu pokeb(0xA000,x+320*y,c), // dar acest Plot este valabil şi pentru rezoluţia 800x600x256, de exemplu.; asm begin MOV AL,Pixel; MOV AH,0CH; MOV CX,X; MOV DX,Y; INT 10H; end end int main(int argc, char **argv) // argumentele liniei de comandă begin FILE *fis; Byte expand[102]; char expandf[8]; unsigned long size, next, frit; Word magic, frames, width, height, aux, flags, speed; unsigned long sizef; Word magicf, chuncks; // CHUNCK unsigned long sizec; char ty1, ty2; Byte pix; int px; // FLI_COLOR Word numpackets; Byte colskip, colchange, red, green, blue; int colc; // FLI_LC Word numlines, changelines; Byte numpack; Byte skip_count; char size_count; // FLI_BRUN Byte nrpack; int szc, skc; int xx, yy; char nume_fli[12]; fpos_t poz_fis; if (argc == 2) then strcpy(nume_fli,argv[1]); else begin fprintf(stderr, "Programul se utilizeaza cu PFLI <nume.fli> .\n"); fprintf(stderr, "Dati <nume.fli> : "); fscanf(stdin,"%s",&nume_fli);

Page 42: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

42

end if ((fis = fopen(nume_fli,"rb")) == NULL) begin fprintf(stderr,"Nu putem deschide acest fisier.\n"); return 1; end // Se iniţializează modul grafic SetVideoMode(19); fread(&size,4,1,fis); fread(&magic,2,1,fis); fread(&frames,2,1,fis); fread(&width,2,1,fis); fread(&height,2,1,fis); fread(&aux,2,1,fis); fread(&flags,2,1,fis); fread(&speed,2,1,fis); fread(&next,4,1,fis); fread(&frit,4,1,fis); fread(&expand,sizeof(&expand),1,fis); for (int i=2; i<=frames; i++) begin // header delay(2*speed); fread(&sizef,4,1,fis); fread(&magicf,2,1,fis); fread(&chuncks,2,1,fis); fread(&expandf,sizeof(&expandf),1,fis); for (int j=1; j<=chuncks; j++) begin fread(&sizec,4,1,fis); fread(&ty1,1,1,fis); fread(&ty2,1,1,fis); switch(ty1) begin case FLI_COLOR: begin fread(&numpackets,2,1,fis); for (long int k=1;k<=numpackets; k++) begin fread(&colskip,1,1,fis); fread(&colchange,1,1,fis); if (colchange==0) then colc=256; else colc=colchange; for (int ii=0; ii<colc; ii++) begin fread(&red,1,1,fis); fread(&green,1,1,fis); fread(&blue,1,1,fis); SetColorRegister(ii,red,green,blue); end end break; end case FLI_LC: begin fread(&numlines,2,1,fis); yy = numlines; fread(&changelines,2,1,fis); if (changelines > height) then changelines /= 256; for (int jj=0; jj<changelines; jj++) begin // o linie compresată jj fread(&numpack,1,1,fis); xx = 0; if (numpack != 0) then

Page 43: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

43

begin for (int pk=1;pk<=numpack;pk++) begin // un pachet pk din linia jj fread(&skip_count,1,1,fis); skc = skip_count; xx += skc; fread(&size_count,1,1,fis); szc = size_count; if (szc > 0) then begin for (px=0; px < szc; px++) begin fread(&pix,1,1,fis); Plot(xx+px,yy+jj,pix); end xx +=szc; end else begin fread(&pix,1,1,fis); for (px=0; px < -szc;px++) Plot(xx+px,yy+jj,pix); xx -=szc; end end end end fgetpos(fis,&poz_fis); if ((poz_fis) % 2) then fread(&pix,1,1,fis); break; end case FLI_BLACK: begin SetVideoMode(19); break; end case FLI_BRUN: begin for (int li=0; li<height; li++) begin xx = 0; fread(&nrpack,1,1,fis); for (Byte pak=1; pak<=nrpack;pak++) begin fread(&size_count,1,1,fis); szc = size_count; if (szc >=0) then begin fread(&pix,1,1,fis); for (px = 0;px < szc;px++) Plot(xx+px,li,pix); xx += szc; end else begin for (px = 0; px < -szc;px++) begin fread(&pix,1,1,fis); Plot(xx+px,li,pix); end xx -= szc; end end end break; end

Page 44: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

44

case FLI_COPY: begin for (int ll = 0; ll < height; ll++) for (int cc = 0; cc < width; cc++) begin fread(&pix,1,1,fis); Plot(cc,ll,pix); end break; end end // switch end end // for ... frame-urile fclose(fis); SetVideoMode(3); return 0; end Suntem convinşi că un bun programator, cu o oarecare practică în domeniul graficii computaţionale, va putea realiza cu uşurinţă un editor grafic de fişiere FLI, care să semene cu Autodesk Animator sau 3D Studio pentru a-şi creea propriile fişiere FLI, după dorinţă, cu tehnicile proprii de desenare. El va trebui să realizeze un editor grafic obişnuit, care să creeze cadrele flic-ului. Apoi, urmînd schema de codificare a cadrelor, va trebui să salveze această succesiune de cadre într-un fişier FLI. Mai întîi se vor salva datele importante despre fişier, în header. Apoi se va salva paleta de culori folosită (FLI_COLOR), urmată de primul cadru desenat (FLI_BRUN). În continuare, se vor determina, diferenţele dintre cadrul curent şi cel anterior, salvîndu-se sub forma de cadru de tip FLI_LC. Fireşte, dacă diferenţele sunt prea mari (ca număr) de pixeli, se va face un cadru de tip FLI_COPY. Dacă se schimbă paleta de la un cadru la altul, atunci un cadru intermediar FLI_COLOR va fi inserat. Dacă ecranul se şterge cumva, la un cadru, se va pune un FLI_BLACK. Mult succes !

Page 45: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

45

Capitolul 3. Afişări deosebite, folosind fişierele de caractere

3.1. Formatul fonturilor CHR Programatorul de grafică dornic să realizeze un afişaj pe oblică, folosind seturile de caractere (fonturile) din fişierele CHR va trebui să cunoască, înainte de toate, ideea care stă la baza acestor fonturi, precum şi formatul fişierelor corespunzătoare. Fonturile din fişierele CHR (numite şi “încondeiate” (stroke fonts, în engleză)) definesc caracterele ca o secvenţă de linii (trăsături), în opoziţie cu fonturile de tip bitmap, care definesc caracterele ca o matrice de puncte. Avantajul fonturilor încondeiate este acela că ele pot fi scalate la mărimi arbitrare şi să-şi menţină rezoluţia lor. Fonturile de tip bitmap sunt făcute pentru o anumită mărime a punctului şi nu pot fi scalate prea bine. De exemplu, dacă aveţi un font de tip bitmap pentru o matrice de 72 puncte pe inch (DPI) şi măriţi de patru ori în înălţime şi în lăţime punctele pentru a utiliza o imprimantă laser cu 300 DPI, atunci pot apărea margini prea mari (largi) în fontul 72 DPI. Pe de altă parte, fonturile de tip stroke, nu sunt convertite în puncte, până cînd rezoluţia perifericului de ieşire nu este cunoscută. Astfel, dacă vreţi să scrieţi cu fonturi de tip stroke pe o imprimantă laser cu 300 DPI, punctele care trasează liniile caracterelor vor fi tipărite la 300 DPI. Acestea, fiind spuse, să trecem la prezentarea structurii unui fişier de tip CHR, exemplificând pe fişierul TRIP.CHR, care este acelaşi pentru toate mediile de programare Borland.

; offset 0h este un header specific firmei Borland: HeaderSize de ex. 080h DataSize de ex. (mărimea fişierului) ; lungimea fiş. CHR descr de ex. "Triplex font" ; descriere fname de ex. "TRIP" ; numele fişierului MajorVersion de ex. 1 ; versiunea fontului MinorVersion de ex. 0 ; subversiunea db 'PK',8,8 db 'BGI ',descr,' V' db MajorVersion+'0' db (MinorVersion / 10)+'0',(MinorVersion mod 10)+'0' db ' - 19 October 1987',0DH,0AH db 'Copyright (c) 1987 Borland International', 0dh,0ah ; (c) db 0,1ah ; null & ctrl-Z = end dw HeaderSize ; mărimea header-ului db fname ; numele fontului dw DataSize ; mărimea fiş. font db MajorVersion,MinorVersion ; numărul de versiune db 1,0 ; nr.e de versiune min. db (HeaderSize - $) DUP (0) ; tampon La offset-ul 80h încep datele pentru fişier, după cum urmează: ; 80h '+' indicatoare pentru tipul fişierului stroke ; 81h-82h numărul de caractere în fişierul font (n) ; 83h nedefinit ; 84h valoarea ASCII a primului caracter din fişier

Page 46: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

46

; 85h-86h offset pt. definiţiile stroke (8+3n) ; 87h scan flag (normal 0) ; 88h distanţa de la origine la vârful literei mari ; 89h distanţa de la origine la linia de bază ; 90h distanţa de la origine la cea mai coborâtă linie ; 91h-95h nedefinit ; 96h offset-uri pentru definiţii individuale de caractere ; 96h+2n tabela grosimilor (un word pentru fiecare caracter) ; 96h+3n startul definiţiilor caracter

Definiţiile individuale de caractere consistă dintr-un număr variabil de cuvinte (word) (16 bits), descriind operaţia necesară în desenarea caracterului. Fiecare word consistă dintr-o pereche de coordonate (x,y) şi două coduri de operaţii pe doi biţi. Cuvintele sunt codificate după cum urmează:

Byte 1 7 6 5 4 3 2 1 0 bit # op1 <coord. X pe 7 biţi cu semn> Byte 2 7 6 5 4 3 2 1 0 bit # op2 <coord. Y pe 7 biţi cu semn>

Codurile de operaţii sunt:

op1=0 op2=0 Sfârşitul definiţiei de caracter. op1=1 op2=0 Mută pointerul grafic în punctul de coordonate (x,y). op1=1 op2=1 Trasează o linie de la punctul curent la punctul de coordonate (x,y).

Descrierea în limbajul C a structurii unui font CHR este dată mai jos: /* FONT.H - informaţiile din header-ul unui fişier de fonturi CHR Copyright (c) 1988,1989 Borland International */ #define Prefix_Size 0x80 #define Major_Version 1 #define Minor_Version 0 #define SIGNATURE ‘+’ enum OP_CODES { END_OF_CHAR = 0, DO_SCAN = 1, MOVE = 2, DRAW = 3 }; typedef struct { char sig; /* SIGNATURE byte */ int nrchrs; /* numărul de caractere in fişier */ char mystery; /* nedefinit incă */ char first; /* primul caracter din fişier */ int cdefs; /* offset la definiţiile de caractere */ char scan_flag;/* True dacă setul de caractere este scanabil */ char org_to_cap;/*inălţimea de la origine la vf. literei mari */ char org_to_base;/* inălţimea de la origine la linia de bază */ char org_to_dec; /* inălţ. de la origine la linia inferioară */ char fntname[4]; /* patru caractere pentru numele fontului */ char unused; /* nedefinit incă */ } HEADER; typedef struct { char opcode; /* Byte pentru codul de operaţie */ int x; /* Offset relativ pe direcţia x */ int y; /* Offset relativ pe direcţia y */ } STROKE;

Page 47: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

47

typedef struct { unsigned int header_size; /* Versiunea 2.0 a formatului de header */ unsigned char font_name[4]; /* Numele intern al fontului */ unsigned int font_size; /* Mărimea în bytes a fişierului */ unsigned char font_major, font_minor; /* Info. de versiune a driverului */ unsigned char min_major, min_minor; /* Informaţii de revizie pentru BGI */ } FHEADER; Pornind de la aceste informaţii, se pot dezvolta proceduri de încărcare a fonturilor şi de afişaj în orice direcţie.

3.2. Rutine speciale de afişare

/* Program pentru afişări speciale (C) 1995 Bogdan Pătruţ */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <graphics.h> #include <conio.h> #include <math.h> #include <string.h> #include "font.h" FILE *ffile;

Page 48: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

48

char *Font; char Prefix[Prefix_Size]; HEADER Header; int Offset[256]; char Char_Width[256]; int POZX = 25, POZY = 25, i; int decode( unsigned int *iptr, int *x, int *y ); /* Această funcţie decodifică formatul fişierului, punând-o într-o structură internă mai convenabilă */ int unpack( char *buf, int index, STROKE **neww ) { unsigned int *pb; STROKE *po; int num_ops = 0; int jx, jy, opcode, i, opc; pb = (unsigned int *)(buf + index); while( FOREVER ){ num_ops += 1; opcode = decode( pb++, &jx, &jy ); if( opcode == END_OF_CHAR ) break; } po = (*neww = (STROKE*)calloc( num_ops, sizeof(STROKE) )); if( !po ){ exit( 100 ); } pb = (unsigned int *)(buf + index); for( i=0 ; i<num_ops ; ++i ){ opc = decode(pb++, &po->x, &po->y); po->opcode = opc; po++; } return( num_ops ); } /* Această funcţie decodifică un singur cuvânt din fişier punându-l într-o structură de tip “stroke” */ int decode( unsigned int *iptr, int *x, int *y ) { struct DECODE { signed int xoff : 7; unsigned int flag1 : 1; signed int yoff : 7; unsigned int flag2 : 1; } cword; cword = *(struct DECODE *)iptr; *x = cword.xoff; *y = cword.yoff; return( (cword.flag1 << 1) + cword.flag2 ); } void WriteChar(char litera, float alfa, int x0, int y0, int xl, int yl) { STROKE *sptr; int j, i = litera; int xx, yy, xxr, yyr; int Caracter = unpack( Font, Offset[i], &sptr ); y0 = getmaxy() - y0; yl = getmaxy() - yl; for( j=0 ; j<Caracter ; ++j, ++sptr ){

Page 49: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

49

xx = xl+sptr->x; yy = yl+sptr->y; xxr = (xx-x0)*cos(alfa) - (yy-y0)*sin(alfa) + x0; yyr = (xx-x0)*sin(alfa) + (yy-y0)*cos(alfa) + y0; if (sptr->opcode == 2) moveto(xxr, getmaxy()-yyr); if (sptr->opcode == 3) lineto(xxr, getmaxy()-yyr); } } void WriteStr(char * cuvint, float alfa, int x0, int y0, int xl, int yl) { POZX = xl; POZY = yl; for (int i=0; i < strlen(cuvint); i++) { WriteChar(cuvint[i], alfa, x0, y0, POZX, POZY); POZX += Char_Width[cuvint[i]] * cos(alfa); POZY -= Char_Width[cuvint[i]] * sin(alfa); x0 += Char_Width[cuvint[i]] * cos(alfa); y0 -= Char_Width[cuvint[i]] * sin(alfa); } } void CircleStr(char * cuvint, int x0, int y0, int raza) { float alfa; int l = strlen(cuvint); int xx, yy; y0 = getmaxy() - y0; for (int i=0; i<l; i++) { alfa = -2*M_PI*i/l; xx = x0+raza * cos(alfa); yy = getmaxy()-y0+raza * sin(alfa); WriteChar(cuvint[i], M_PI/2-alfa, xx, yy, xx, yy); } } void SinWrite(char * cuvint, int x, int y, int raza) { float alfa; int l = strlen(cuvint); for (int i=0; i<l; i++) { alfa = -4*M_PI*i/l; y = y + raza * sin(alfa); WriteChar(cuvint[i], 0, x, y, x, y); x += Char_Width[cuvint[i]]; } } void main() { long length, current; char *cptr; STROKE *sptr; ffile = fopen( "scri.chr", "rb" ); if( NULL == ffile ){ exit( 1 ); } fread(Prefix, Prefix_Size, 1, ffile); cptr = Prefix; while( 0x1a != *cptr ) ++cptr; *cptr = '\0';

Page 50: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

50

fread(&Header, sizeof(HEADER), 1, ffile); fread(&Offset[Header.first], Header.nchrs, sizeof(int), ffile ); fread(&Char_Width[Header.first],Header.nchrs, sizeof(char), ffile); current = ftell( ffile ); fseek( ffile, 0, SEEK_END ); length = ftell( ffile ); fseek( ffile, current, SEEK_SET ); Font = (char *) malloc( (int) length ); if( NULL == Font ){ fprintf( stderr, "Memorie insuficientă.\n\n" ); exit( 1 ); } fread( Font, (int)length, 1 , ffile ); fclose(ffile); int gd,gm; gd = 0; initgraph(&gd,&gm,"c:\\bc\\bgi"); rectangle(0,0,getmaxx(),getmaxy()); for (i=-3; i<3; i++) { setcolor(i); if (!i) setcolor(CYAN); WriteStr(" Try the",-M_PI*i/3,320,200,320,200); } setcolor(WHITE); CircleStr("HICS ! * BEST GRAP",320,200,80); setcolor(LIGHTCYAN); SinWrite("Author Bogdan Pătruţ, tel. 0234/206090",10,470,10); getch(); closegraph(); }

Page 51: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

51

Capitolul 4. Despre jocurile pe calculator

Motto: “Jocul este o punte aruncată

între copilărie şi vârsta matură” (J. Chateau) Credem că cititorul este conştient de importanţa pe care o au jocurile în educarea omului, încă din anii copilăriei. Calculatoarele, prin posibilităţile lor de a face rapid o serie întreagă de calcule şi de a reprezenta grafic diferite obiecte din natură, pot face aşa încât să simuleze pe ecranele lor aproape orice joc care poate fi practicat şi altfel, de la jocurile de cărţi sau zaruri, până la şah sau go, de la jocurile cu cuvinte, până chiar la jocurile sportive, deoarece pe calculator pot fi simulate şi animate o serie întreagă de activităţi din lumea reală, din natură. Putem spune, astfel, că jocurile pe calculator reprezintă în mic, pe ecran, lumea mare reală. Ce implică un joc pe calculator ? Această problemă are două componente:

• ce presupune realizarea unui joc pe calculator ? • ce implică existenţa unui joc pe calculator ?

4.1. Ce presupune realizarea unui joc pe calculator Răspunsul la prima întrebare este scurt: muncă. Într-adevăr, din punctul de vedere al

programatorilor, un joc pe calculator este considerat o întreprindere cât se poate de serioasă, un produs soft foarte complex, care presupune, în general, formarea unei echipe de programatori, ingineri, designeri, etc., care vor trebui să-şi adune forţele, inteligenţa şi imaginaţia la un loc, pentru a imagina, a gândi, a proiecta, a programa un joc şi a-i da un aspect cât mai comercial. Realizarea de unul singur a unui joc pe calculator este anevoioasă, şi fie duce la un eşec, fie duce la un rezultat mai puţin impresionant. Este ceea ce veţi constata şi după ce veţi fi tastat şi rulat jocurile prezentate în această lucrare. Deşi, în esenţă, foarte frumoase şi distractive, e de ajuns să ne gândim o clipă la suita de jocuri multicolore şi multi-animate care vin din ţările occidentale sau din Rusia, pentru a realiza că, de fapt, nu am atins performanţele acelor jocuri. Însă, muncind în continuare, colaborând între noi sau cu prietenii, vom putea obţine rezultate nu doar satisfăcătoare, ci chiar foarte bune. Introducerea de melodii în cadrul jocurilor noastre, de efecte speciale (obţinute, de cele mai multe ori, cu nişte rutine scurte în limbaj de asamblare), de imagini cât mai sugestive şi mai viu colorate va face, cu siguranţă, jocul nostru mai atractiv, mai apreciat, mai căutat astfel încât să poată ajunge să circule între împătinmiţii din ţară şi poate... nu numai ! În mare măsură, alături de programatorii propriu-zişi ai jocului, mai trebuie să se ocupe cineva de efectele speciale (de sunet, grafică, animaţie), cineva de editarea desenelor (şi aici trebuie să fie o persoană cu mult talent în artele plastice), cineva de melodiile care vor fi încorporate în joc (şi aici trebuie să fie o persoană care se pricepe şi la muzică şi la programarea ei pe calculator), cineva de aspectul comercial al jocului (un designer, care să îmbine armonios toate elementele decorative ale jocului, fie ele vizuale, fie auditive). În fine, cineva va trebui să elaboreze o documentaţie a jocului, cât mai explicită cu putinţă, iar o altă persoană să se ocupe de testarea jocului, prin rularea programului în situaţii cât mai diverse. Referitor la a doua întrebare, existenţa unui joc pe calculator înseamnă existenţa unui nou mod de recreere, care poate fi util în pauzele de serviciu ale persoanelor care utilizează calculatoare personale în activitatea lor profesională. Dacă jocul este de logică, atunci el va ajuta la dezvoltarea

Page 52: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

52

gândirii şi puterii de deducţie şi/sau inducţie a jucătorului, aşa cum o fac de exemplu problemele de perspicacitate sau enigmistică. Există însă şi situaţii în care, practicarea jocurilor pe calculator stârneşte pasiuni nebune sau competiţii între diferite persoane, care pot deveni ecrano-dependente folosind în mod abuziv calculatoarele pentru jocuri, riscând în a-şi deteriora chiar sănătatea. De aceea ne pronunţăm pentru realizarea de jocuri pe calculator, ca un exerciţiu util şi plăcut de programare, dar împotriva exagerării folosirii calculatoarelor pentru jocuri.

4.2. Ce jocuri putem realiza împreună, pe calculator ?1 La această întrebare se poate răspunde simplu, printr-un singur cuvânt: multe. Într-adevăr există o multitudine de jocuri ce se pot realiza pe calculator relativ simplu, folosind chiar numai limbajul Turbo Pascal. De pildă, putem să realizăm simulări grafice de jocuri reale, care se dispută între doi parteneri, însă în lucrarea de faţă ne vom referi la acele jocuri ce sunt caracteristice calculatorului, cum ar fi jocurile de animaţie, de îndemânare, de grafică şi prelucrări de imagini. După cum am arătat în capitolul precedent, realizarea unui joc performant, cel puţin în ceea ce priveşte elementele grafice, este o treabă foarte serioasă, foarte dificilă şi foarte obositoare. Iar, de cele mai multe ori, munca cea mai mare se depune în legătură cu toate imaginile grafice care apar pe parcursul jocului, fie ca imagini de fundal (decor), fie ca imagini reprezentând diferite obiecte ce se mişcă sau nu pe acest fundal. Or, în asemenea situaţii, este dificil de transmis, pe calea unei cărţi ca cea de faţă imagini... Există, însă şi jocuri care nu necesită elemente deosebite de grafică, cum ar fi jocurile în modul text, sau unele jocuri de grafică, cum ar fi binecunoscutul joc de îndemânare, dar în acelaşi timp, de logică, Tetris. În capitolele ce urmează ne vom ocupa de astfel de jocuri, care, deşi vor fi realizate relativ simplu, pot să vă distreze la fel de bine ca şi un joc “de firmă” şi, în plus, pot constitui un bogat material didactic. Cu siguranţă, aceste jocuri, pe care ne-am străduit să le explicăm pe cât posibil de bine, vor putea fi extinse şi perfecţionate de dumneavoastră, pentru a face din ele adevărate jocuri “de firmă”. De pildă, jocurile utilizând unit-ul CRT vor putea fi transformate relativ uşor - prin rescrierea procedurilor de afişare / ştergere - în jocuri care să se bazeze pe unit-ul GRAPH şi să funcţioneze în modul grafic. Se pot adăuga efecte sonore, culori, etc.. Relativ la capitolul 5, acolo sunt prezentate două jocuri cu reguli foarte simple, realizate şi ele foarte simplu, dar care pot impresiona mult mai mult ca altele, dacă sunt folosite imagini din fişiere BMP, care să cuprindă imagini cu fete sexy, de exemplu. Astfel de imagini se pot obţine din diferite surse, decupate fiind, apoi, la dimensiunile cerute în program, cu un utilitar de captură de imagini, cu ar fi Pizzaz Plus (PZP). Dacă nu dispuneţi de imagini în format BMP, dar dispuneţi de imagini în format PCX, atunci va trebui să le convertiţi în fişiere cu formatul BMP, folosind utilitare specializate, cum ar fi VPIC (cu comanda W, după ce a fost vizualizat respectivul PCX). Acelaşi lucru e valabil dacă dispuneţi de imagini în alte formate: GIF, PIC, TIF etc.. Dacă nu dispuneţi de VPIC, atunci încercaţi cu un program din Windows, chiar şi cu Paint Brush, în ultimă instanţă. Fireşte, noi presupunem că dumneavoastră aveţi experienţă în lucrul cu programe de prelucrări grafice şi dispuneţi şi de o bibliotecă de imagini, dacă nu sexy, atunci nişte imagini oarecare :). Dacă însă nu dispuneţi de asemenea imagini, printr-un mail dat autorilor vă puteţi procura chiar fişierele BMP care au fost folosite când au fost realizate programele.

1 Îi rugăm pe cei mai puţini cunoscători în lucrul cu formatele grafice să citească neapărat acest capitol, pentru a nu avea neplăceri, mai târziu.

Page 53: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

53

Precizările ulterioare (referitoare la jocurile Amestec şi Spânzurătoarea) rămân valabile şi în cazul jocului Feţe, care foloseşte un fişier BMP, precum şi o serie de fişiere cu extensia IMA, fişiere cu imagini în formatul recunoscut de procedurile PutImage şi GetImage din Turbo Pascal 7.0, imagini care sunt, de fapt, transformate din format BMP în acest format, cu ajutorul programului BMP2IMA, care este dat mai jos.

program BitMapToPascalImages; { BMP2IMA.PAS } uses Crt,Graph,ViewBMP,uMouse; procedure SalvImag(fisier: String; imag: Pointer; nrbytes: Word); var imagefisier: File; control: Integer; begin Assign(imagefisier,fisier); ReWrite(imagefisier); BlockWrite(imagefisier,imag^,(nrbytes Div 128)+1,control); Close(imagefisier); FreeMem(imag,nrbytes) end; procedure ApelImag(fisier:string;var imag:Pointer;nrbytes:word); var imagefisier:File; control:Integer; begin GetMem(imag,nrbytes); Assign(imagefisier,fisier); Reset(imagefisier); BlockRead(imagefisier,imag^,(nrbytes Div 128)+1,control); Close(imagefisier) end; var Size:Word; P:Pointer; nume_fis: String; gd,gm,b,x1,y1,x2,y2: Integer; begin Write('Nume BMP : '); ReadLn(nume_fis); gd:=Detect; InitGraph(gd,gm,'C:\TP\BGI'); LoadPackBMPFile(0,0,nume_fis+'.BMP'); MouseInit; repeat MouseData(b,x1,y1) until b=1; SetWriteMode(1); MouseHide; { zona de imagine care trebuie transformata se selecteaza ca o zona dreptunghiulara, cu ajutorul mouse-ului } repeat MouseData(b,x2,y2); Rectangle(x1,y1,x2,y2); MouseShow; Delay(50); MouseHide; Rectangle(x1,y1,x2,y2) until b=0; Size:=ImageSize(x1,y1,x2,y2); GetMem(P,Size); GetImage(x1,y1,x2,y2,P^); SalvImag(nume_fis+'.IMA',P,Size); ClearViewPort; ApelImag(nume_fis+'.IMA',P,Size); PutImage(0,0,P^,NormalPut); FreeMem(P,Size); ReadLn; CloseGraph; end.

Page 54: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

54

Programul foloseşte un unit de lucru cu mouse-ul în modul grafic (uMouse), unit care poate fi preluat fie din anexa cărţii, fie din lucrarea “Grafică în OOP ... şi nu numai...”, citată la bibliografie. Unitul uMouse va fi folosit şi în § 6.3, pentru a edita unele imagini, determinate de mai multe linii poligonale, imagini ce vor fi transformate în altele. Dacă totuşi nu dispuneţi de BMP-uri de dimensiunile cerute în programele citate, dar v-ar interesa să vedeţi o rulare a lor, atunci, vă propunem să folosiţi unit-ul ViewBMP, prezentat la finele cărţii, în anexă, precum şi următoarele două programe care redimensionează bitmap-uri, fie că ele sunt folosite de grafica în 16 culori, cu rezoluţia de 640 × 480 (sau 640 × 350), deci lucrând cu unit-ul GRAPH, fie de grafica în 256 culori, cu rezoluţia de 320 × 200, deci lucrând cu unit-ul MCGA, unit prezentat în aceeaşi anexă de la sfârşitul cărţii. Astfel, presupunând că nu se poate să nu dispuneţi de fişierele ‘CARS.BMP’ (16 culori) şi ‘256COLOR.BMP’, care se găsesc în subdirectorul WINDOWS, folosiţi, aşa cum se arată mai jos, procedurile Măreşte şi Micşorează, până obţineţi imaginea respectivă în dimensiunile dorite. După care apelaţi, după caz, SavePackBMPFile, respectiv SaveUnPackBMPFile, pentru a obţine pe disc fişiere BMP cu aceeaşi imagine, dar de proporţiile necesare. Puţine calcule, multă atenţie şi, după câteva încercări, succesul e garantat, astfel încât veţi putea să folosiţi jocurile Feţe, Amestec şi Spânzurătoarea, prezentate în § 6.2, § 7.1, respectiv § 7.2.

* Procedurile care realizează redimensionarea unei imagini de pe ecran sunt grupate în fişierul RESIZE.INC, fişier folosit, prin includere cu directiva de compilare {$I nume_fis_de_inclus}, de ambele programe, atât de cel pentru unit-ul GRAPH, cât şi de cel ce foloseşte unit-ul nostru de grafică MCGA.

{ fisierul RESIZE.INC } procedure SizePlot(x,y: Integer; cul,sizex,sizey: Byte); { desenează, de fapt, un dreptunghi umplut, care reprezintă un fel de PutPixel al unui punct mărit de sizex ori pe orizontală şi de sizey ori pe verticală } var i,j: Byte; begin for i:=0 to sizex-1 do for j:=0 to sizey-1 do PutPixel(x+i,y+j,cul) end; procedure Micsoreaza(x1,y1,x2,y2: Integer; coefx,coefy: Byte); { se micşorează imaginea din zona dreptunghiulară dată de colţul stânga-sus = (x1,y1) şi colţul dreapta-jos = (x2,y2); coeficienţii de redimensionare sunt coefx (pe orizontală), respectiv coef(y), pe verticală; pentru o injumătăţire, pe ambele direcţii a imaginii, se va apela procedura aceasta cu coeficienţii 2 si 2; micşorarea imaginii poate duce la pierderea de informaţii utile din cadrul imaginii; astfel, apelarea unei măriri după o micşorare nu inseamnă neapărat restaurarea imaginii iniţiale ! de fapt, procedura Micsoreaza sare peste unii pixeli; imaginea rezultată, va avea colţul stânga-sus tot în (x1,y1), ca şi imaginea iniţială } var c,x,y,i,j: Integer; begin i:=x1-1; x:=x1; while x<=x2 do begin Inc(i); j:=y1-1;

Page 55: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

55

y:=y1; while y<=y2 do begin Inc(j); c:=GetPixel(x,y); PutPixel(i,j,c); Inc(y,coefy){se sare pe direcţia y cu coefy} end; Inc(x,coefx) {se sare pe direcţia x cu coefx pixeli} end; ClearView(i+1,y1,x2,y2); { se curăţă zona de ecran ramasă } ClearView(x1,j+1,i,y2) end; procedure Mareste(x1,y1,x2,y2: Integer; coefx,coefy: Byte); { măreşte imaginea din zona dreptunghiulară dată de colţurile (x1,y1) si (x2,y2), cu coeficienţii specificaţi pe x si y } var x,y,xn,yn: Integer; c: Byte; begin for x:=x2 downto x1 do for y:=y2 downto y1 do begin c:=GetPixel(x,y); { se calculează coordonatele noului punct, unde se va face o afişare de pixel mărită; parcurgerea imaginii se face dinspre colţul dreapta jos, pentru a nu se pierde pixeli; imaginea rezultată va avea acelaşi colţ stânga sus, ca şi imaginea iniţială (x1,y1) } xn:=x*coefx+x1*(1-coefx); yn:=y*coefy+y1*(1-coefy); SizePlot(xn,yn,c,coefx,coefy) end end;

Iată, mai jos, cele două programe exemplu, care folosesc aceste proceduri pentru a face redimensionări ale celor două bitmap-uri pe care presupunem că le aveţi: pentru 16 culori:

program RedimensionareBMP16Culori; uses Graph, ViewBMP; procedure OpenGraph; { iniţializare mod grafic } var gd,gm: Integer; begin gd:=Detect; InitGraph(gd,gm,'C:\TP\BGI') end; procedure ClearView(x1,y1,x2,y2: Integer); { şterge un ViewPort } begin SetViewPort(x1,y1,x2,y2, ClipOn); ClearViewPort; SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn) end; {$I RESIZE.INC} begin OpenGraph; LoadPackBMPFile(0,0,'CARS.BMP');

Page 56: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

56

ReadLn; Mareste(0,0,40,50,7,5); ReadLn; Micsoreaza(0,0,40*7,50*5,7,2); ReadLn; CloseGraph end.

... şi pentru 256 culori:

program RedimensionareBMP256Culori; uses MCGA, ViewBMP; {$I RESIZE.INC} begin OpenGraph; LoadUnPackBMPFile(0,0,'256COLOR.BMP'); ReadLn; Mareste(0,0,80,50,3,2); ReadLn; Micsoreaza(0,0,80*3,50*2,5,2); ReadLn; CloseGraph end.

Puteţi să utilizaţi procedurile de redimensionare a imaginilor, precum şi cele două proceduri Save(Un)PackBMPFile pentru a prelucra singuri fişierele BMP de care dispuneţi. Apoi le veţi putea folosi în cele trei jocuri despre care am vorbit sau în altele similare, pe care dumneavoastră înşivă le veţi putea realiza ulterior. O ultimă precizare, foarte importantă. În toată lucrarea, se consideră calea grafică (către fişierele BGI şi CHR) ca fiind ‘C:\TP\BGI’1. Cei ce au altă cale grafică la calculatoarele lor, vor face modificările cuvenite, sau vor aduce fişierele CHR şi fişierul EGAVGA.BGI în directorul curent, de lucru.

*

Putem realiza multe jocuri pe calculator, dar, după cum vă spuneam, nu este vorba despre un lucru uşor, ci despre o problemă de programare foarte dificilă, pentru care paginile acestei modeste lucrări ar fi neîncăpătoare.

1 pentru grafica în C, calea grafică considerată este C:\BORLANDC\BGI’.

Page 57: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

57

Capitolul 6. Jocuri folosind unit-ul CRT

Acest capitol se ocupă de un anumit gen de jocuri, cu o animaţie simplă, realizată în modul text, deci folosind unit-ul standard CRT. Deşi nu impresionează vizual, jocurile în modul text pot fi prima etapă în realizarea de jocuri mai performante, folosind imagini grafice, etc. De pildă, chiar jocul Feţe, prezentat în paragraful 7.2, iniţial a fost realizat în mod text, apoi a fost îmbogăţit prin ataşarea de imagini grafice, luate din câteva bitmap-uri. Pe lângă acestea, unit-ul CRT dispune de multe proceduri şi funcţii, care ar putea fi folosite pentru realizarea de jocuri de logică.

5.1. Animaţie la “Turnurile din Hanoi”

Spre surprinderea mea, în calitate de profesor de informatică, am avut mulţi elevi care nu auziseră de problema Turnurilor din Hanoi, problemă clasică în informatică, a cărei rezolvare este prezentată în multe cărţi ca o aplicaţie la metoda generală de programare “divide et impera”. Această tehnică constă în următoarele: o problemă, dacă este simplă, se rezolvă pur şi simplu, deci se comunică soluţia ei. Dacă problema este, însă, destul de complexă (pentru a o putea rezolva direct), dar ea se poate descompune în două sau mai multe probleme similare, de dimensiuni mai mici, atunci se rezolvă acele probleme, soluţia problemei mari obţinându-se printr-o anumită combinare a soluţiilor problemelor mici. La rândul lor, problemele mici se pot descompune în subprobleme, deci procedeul este recursiv. Această metodă va fi folosită în continuare, pentru a soluţiona problema turnurilor din Hanoi. Care este problema, însă ? Se spune că în Vietnamul antic, în Hanoi, erau trei turnuri, pe unul din ele fiind puse, în ordinea descrescătoare a diametrelor lor, mai multe discuri (8) din aur. Din motive obiective, nişte călugări, care le aveau în grijă, trebuiau să mute discurile pe cel de-al doilea turn, în aceeaşi ordine, însă trebuiau să se folosească, eventual, de turnul al treilea, deoarece altfel discurile nu ar fi avut stabilitate. Discurile puteau fi mutate unul câte unul. De asemenea, se renunţa din start la ideea de a aşeza un disc mai mare peste unul mai mic, deoarece exista riscul deteriorării discurilor, din cauza diferenţei mari de masă. Deşi, aparent simplă, după câteva încercări, cu un prototip în faţă, cititorul va constata că problema nu e banală. Însă este posibilă o rezolvare optimă a ei (cu numai 2n mutări, deci 255, pentru n=8), folosind tehnica recursivă “divide et impera”. Astfel, problema iniţială este de a muta n discuri de la turnul 1 la turnul 2. În general, cum se mută m discuri de la un turn p la un turn q ? Ei bine, se mută primele m-1 discuri de pe p pe r, r fiind turnul auxiliar, apoi singurul disc rămas pe p - discul cel mai mare, se mută de la p la q, după care cele m-1 discuri sunt mutate de pe r pe q. Fireşte, mutarea celor m-1 discuri de la p la r şi de la r la q este făcută prin aceeaşi metodă, deci recursiv, şi, în plus şi foarte important, mutarea primelor m-1 discuri este corectă, deoarece existenţa discurilor de diametre mai mari, la bazele celor trei turnuri nu afectează cu nimic mutările discurilor mai mici. În cazul limită m=1, nu avem de a face decât cu o mutare pur şi simplu de la p la q a discului din vârful turnului p. Mai jos este prezentat un program care rezolvă această problemă în cazul n=8 (programul poate fi uşor generalizat), prezentând şi o plăcută animaţie. Fiind realizat foarte sugestiv, nu mai comentăm programul.

program TurnurileDinHanoi; uses Crt;

Page 58: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

58

const Pauza=50; forma='Û'; { #78 } var Virf: array [1..3 ] of Byte; procedure HideCursor; assembler; { ascunde cursorul pâlpâitor, din modul text } asm MOV AX,$0100; MOV CX,$2607; INT $10 end; procedure ShowCursor; assembler; { reafişează cursorul } asm MOV AX,$0100; MOV CX,$0506; INT $10 end; function ColTija (tija : Byte) : Byte; begin ColTija := 24*tija-8 end; procedure MutaDreapta (disc, tija1, tija2 : Byte); var i,k: Byte; begin for i := ColTija(tija1)-disc to Pred(ColTija(tija2)-disc) do begin Delay(Pauza); if KeyPressed then Halt(1); GoToXY(i,3); for k:=0 to 2*disc do Write(' '); GoToXY(i+1,3); for k:=0 to 2*disc do Write(forma) end end; procedure MutaStinga (disc, tija1, tija2 : Byte); var i,k: Byte; begin for i := ColTija(tija1)-disc downto Succ(ColTija(tija2)-disc) do begin Delay(Pauza); if KeyPressed then Halt(1); GoToXY(i,3); for k:=0 to 2*disc do Write(' '); GoToXY(i-1,3); for k:=0 to 2*disc do Write(forma) end end; procedure Coboara (disc, tija : Byte); var i,k: Byte; begin for i := 3 to Pred(Virf[tija]-1) do begin Delay(Pauza); if KeyPressed then Halt(1);

Page 59: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

59

GoToXY(ColTija(tija)-disc,i); for k:=0 to 2*disc do Write(' '); GoToXY(ColTija(tija)-disc,i+1); for k:=0 to 2*disc do Write(forma) end; Dec(Virf[tija]) end; procedure Ridica (disc, tija : Byte); var i,k: Byte; begin for i := Virf[tija] downto 4 do begin Delay(Pauza); if KeyPressed then Halt(1); GoToXY(ColTija(tija)-disc,i); for k:=0 to 2*disc do Write(' '); GoToXY(ColTija(tija)-disc,i-1); for k:=0 to 2*disc do Write(forma) end; Inc(virf[tija]) end; procedure Muta(disc, tija1, tija2 : Byte); begin Ridica(disc,tija1); if (tija1 < tija2) then MutaDreapta(disc,tija1,tija2) else MutaStinga(disc,tija1,tija2); Coboara(disc,tija2) end; procedure Han(n, tija1, tija2, tija3 : Byte); begin if (n = 1) then Muta(1,tija1,tija2) else begin Han(n-1,tija1,tija3,tija2); Muta(n,tija1,tija2); Han(n-1,tija3,tija2,tija1) end end; procedure Initializari; var k,disc: Byte; begin HideCursor; Virf[1]:=13; Virf[2]:=22; Virf[3]:=22; ClrScr; for disc:=1 to 9 do begin GoToXY(ColTija(1)-disc,Virf[1]+disc-1); for k:=0 to 2*disc do

Page 60: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

60

Write(forma); end end; begin { PROGRAM } Initializari; GoToXY(28,1); WriteLn(' ÎTurnurile din HanoiÎ '); Han(8,1,2,3); ShowCursor end.

Procedura de bază din program este Han, iar celelalte proceduri sunt pentru mişcarea discului care se mută. Aţi observat că există şi două proceduri realizate în limbaj de asamblare, care folosind întreruperea 10h, realizează ascunderea, respectiv reafişarea cursorului text. Ascunderea cursorului, ca şi în cazul de mai înainte, este foarte folositoare atunci când se realizează animaţii în modul text. Încercaţi, de pildă să folosiţi aceste proceduri şi în cazul jocului ce urmează (Bila n.a.), pentru a elimina cursorul deranjator de pe ecran, în timpul deplasărilor obiectelor din joc.

Metoda de soluţionare a problemei “Turnurilor din Hanoi”.

Fireşte, problema de mai înainte nu este un joc propriu-zis, ci mai degrabă o problemă de perspicacitate, dar poate fi privită şi ca un joc de logică pentru un singur jucător, aşa cum este şi cubul lui Rubik.

Page 61: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

61

5.2. Bila

În cele ce urmează, vă vom prezenta un simplu joc pentru doi jucători, de fapt unicul joc de acest gen din cartea de faţă. Cei doi dispun de două palete verticale, care se mişcă în plan vertical şi trebuie să-şi păzească, fiecare, propria poartă, care se află fie în stânga, fie în dreapta ecranului, de o bilă. Această bilă, se mişcă neîncetat în terenul figurat pe ecran, ea respingându-se, conform legilor fizicii referitoare la reflexie, ori de câte ori întâlneşte un obstacol, fie că acesta este unul din pereţii ce compun marginea terenului de joc, fie zidul vertical care se află în mijlocul terenului, fie paletele celor doi jucători. Însă, în cazul paletelor, acestea îi pot imprima o nouă viteză verticală bilei, ca rezultat al compunerii dintre viteza pe verticală a bilei şi cea a paletei. În orice caz, aceasta variază între -2 şi +2. Toate aceste viteze, ca şi liniile şi coloanele diferitelor obiecte (bilă, palete) au numele sugestive, începând cu “Depl”, de la deplasare. Bila lasă o urmă reprezentată de 5 caractere •. Pentru a juca, cel din stânga foloseşte tastele Q (sus) şi A (jos), iar jucătorul din dreapta tastele P (sus) şi L (jos). Se joacă până bila intră de 5 ori într-una din porţi. Ecranul calculatorului, într-o poziţie oarecare de joc, arată cam în genul următor (vezi figura!), iar programul Bila este bogat comentat. Distracţie plăcută, alături de un prieten!

Imagine din timpul unei partide de... “BILA”.

program Bila; uses Crt; type pozitie = record linie: Integer; coloana: Integer end ; var ColBila,LinBila,ColNouaBila, LinNouaBila,DeplColBila,DeplLinBila: Integer; LinPal1,LinPal2,DeplLinPal1,DeplLinPal2,PctJuc1,PctJuc2: Integer; Tasta: Char; i: Integer; Sonor, GataRepriza,GataJoc: Boolean; urma: array [1..5] of pozitie;

Page 62: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

62

procedure PrintAt(x,y: Integer; S: String); begin GoToXY(x,y); Write(S) end; procedure Beep(n,m: Integer); begin if Sonor then begin Sound(n); Delay(5*m); NoSound end end; procedure Chenar; begin ClrScr; for i:=1 to 80 do begin PrintAt(i,1,'²'); PrintAt(i,24,'²') end; for i:=1 to 7 do begin PrintAt(1,i,'²'); PrintAt(1,25-i,'²'); PrintAt(80,i,'²'); PrintAt(80,25-i,'²') end; for i:=10 to 15 do PrintAt(40,i,'²') end; procedure Mesaje; var s: String; begin Write('Doriti sonor ? [DA/NU] > '); ReadLn(s); Sonor := not (UpCase(s[1])='') end; procedure Initializeaza; begin ColBila:=3+random(10);LinBila:=3+random(10); for i:=1 to 5 do begin urma[i].coloana:=ColBila; urma[i].linie:=LinBila end; DeplColBila := Random(3)-1; DeplLinBila := Random(3)-1; if (DeplColBila*DeplLinBila=0) then begin DeplColBila := 1; { implicit dreapta - jos } DeplLinBila := 1 end; LinPal1:=12;LinPal2:=12;DeplLinPal1:=0;DeplLinPal2:=0; for i:=-2 to 2 do begin PrintAt(2,LinPal1+i,'±'); PrintAt(79,LinPal2+i,'±') end end;

Page 63: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

63

procedure MutaPaletele; begin if KeyPressed then begin Tasta := ReadKey; { ştergere palete din poziţia veche } for i:=-2 to 2 do begin PrintAt(2,LinPal1+i,' '); PrintAt(79,LinPal2+i,' ') end; { actualizare poziţii palete, în funcţie de Tasta } case Tasta of 'q': Dec(LinPal1); 'a': Inc(LinPal1); 'p': Dec(LinPal2); 'l': Inc(LinPal2) end; {case} { nu trebuie depăşite marginile de către palete } if LinPal1>21 then LinPal1:=21; if LinPal1<4 then LinPal1:=4; if LinPal2>21 then LinPal2:=21; if LinPal2<4 then LinPal2:=4; { redesenare a paletelor în noua poziţie } for i:=-2 to 2 do begin PrintAt(2,LinPal1+i,'±'); PrintAt(79,LinPal2+i,'±') end end end; procedure MutaBila; begin MutaPaletele; { lovire margini de sus sau de jos } if (LinBila = 23) or (LinBila = 2) then begin DeplLinBila := -DeplLinBila; { schimbare sens de deplasare } Beep(100,10) end; { lovire margini din stânga sau din dreapta } { bila este in apropierea zidului din stânga ... } if ColBila = 3 then { se testează dacă paleta 1 nu e în apropiere } if abs(LinPal1-LinBila) <= 2 then begin { dacă bila este lovită de paleta 1, atunci aceasta îi poate schimba viteza, însă viteza se incadrează în limita -2 .. +2 } if Tasta = 'q' then DeplLinBila := DeplLinBila - 1; if Tasta = 'a' then DeplLinBila := DeplLinBila + 1; if DeplLinBila <- 2 then DeplLinBila := -2; if DeplLinBila >+ 2 then DeplLinBila := +2; DeplColBila := -DeplColBila;

Page 64: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

64

{ schimbare sens de deplasare } Beep(450,10) end; if ColBila = 2 then { s-a marcat în poarta 1 ? } if ((LinBila >= 2) and (LinBila <= 7)) or ((LinBila <= 23) and (LinBila >= 18)) then { nu s-a marcat, ci s-a atins zidul, deci se schimbă sensul } DeplColBila := -DeplColBila else begin { s-a marcat in poarta juc.1, deci jucătorul 2 primeşte puncte } for i := 1 to 10 do Beep(500-25*i,10); Inc(PctJuc2); GataRepriza := True { s-a terminat incă o repriză } end; { condiţii şi acţiuni analoage şi în cazul zidului din dreapta } if ColBila = 78 then if abs(LinPal2-LinBila) <= 2 then begin if Tasta = 'p' then DeplLinBila := DeplLinBila - 1; if Tasta = 'l' then DeplLinBila := DeplLinBila + 1; if DeplLinBila <- 2 then DeplLinBila := -2; if DeplLinBila > +2 then DeplLinBila := +2; DeplColBila := -DeplColBila; Beep(450,10) end; if ColBila = 79 then if ((LinBila >= 2) and (LinBila <= 7)) or ((LinBila <= 23) and (LinBila >= 18)) then DeplColBila := -DeplColBila else begin for i := 1 to 10 do Beep(500-25*i,10); Inc(PctJuc1); GataRepriza := True end; { lovire centru ... } if ((LinBila > 9) and (LinBila < 16)) and ((ColBila = 39) or (ColBila = 41)) then begin DeplColBila := -DeplColBila; Beep(600,10) end; ColNouaBila := ColBila + DeplColBila; if ColNouaBila < 2 then ColNouaBila := 2; if ColNouaBila > 79 then ColNouaBila := 79; LinNouaBila := LinBila + DeplLinBila; if LinNouaBila < 2 then LinNouaBila := 2; if LinNouaBila > 23 then LinNouaBila := 23; { ştergem bila din poziţia veche } PrintAt(ColBila,LinBila,' '); { actualizăm urma şi o afişăm... } for i := 1 to 4 do begin urma[i] := urma[i+1];

Page 65: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

65

PrintAt(urma[i].coloana,urma[i].linie,'ú') end; PrintAt(urma[1].coloana,urma[1].linie,' '); { coada urmei } urma[5].coloana := ColBila; { capul urmei } urma[5].linie := LinBila; { bila se afişează la noile coordonate } PrintAt(ColNouaBila,LinNouaBila,'o'); ColBila := ColNouaBila; LinBila := LinNouaBila; Delay(45); MutaPaletele; end; begin {** PROGRAM **} ClrScr; { mărire viteză cursor } Port[$60] := $F3; Delay(200); Port[$60] := 0; PctJuc1 := 0; PctJuc2 := 0; mesaje; GataJoc := False; repeat GataRepriza := false; Chenar; Initializeaza; MutaPaletele; repeat MutaPaletele; MutaBila; MutaPaletele; if GataRepriza then begin GoToXY(30,24); Write(' SCOR : ',PctJuc1:2, ' -- ',PctJuc2:2,' '); Delay(1500); Chenar end; if (PctJuc1=5) or (PctJuc2=5) then GataJoc:=true until GataRepriza until GataJoc; PrintAt(32,7,'* JOC TERMINAT *'); GoToXY(32,8); Write('SCOR : ',PctJuc1:2,' -- ',PctJuc2:2); GoToXY(20,22); if PctJuc1<PctJuc2 then Write('JUCĂTORUL DIN DREAPTA A CÂŞTIGAT .') else Write('JUCĂTORUL DIN STÂNGA A CÂŞTIGAT .'); GoToXY(1,1); repeat until KeyPressed; ClrScr end.

Şi acest program, ca şi cel anterior, conţine un element special, şi anume mărirea vitezei de receptare a tastelor, cu ajutorul portului $60:

Port[$60] := $F3; Delay(200); Port[$60] := 0;

Page 66: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

66

Veţi putea folosi cu succes acest mic “truc” şi în alte programe de-ale dumneavoastră, care necesită citiri dese de la tastatură. Propunem cititorului ca exerciţiu să scrie acest program obiectual, în cazul în care are cunoştinţe minime de programare orientată pe obiecte. Un model de joc folosind unit-ul CRT şi programarea orientată pe obiecte este programul următor, Navele.

5.3. Navele

Cunoaşteţi jocul Invadatorii? Este vorba despre un simplu joc, în care o navă proprie (numită om) trebuia să nimicească nişte nave adverse (numite invadatori), care se mişcau orizontal şi coborau spre om. De această dată, navele se mişcă aleator pe ecran, dar nava omului se mişcă tot orizontal, în partea de jos a ecranului. Aceste nave trebuie distruse într-un anumit interval de timp. Modificaţi programul astfel încât şi ele să tragă gloanţe în om şi să-l poată distruge. Programul Navele, pe care îl prezentăm în acest paragraf, foloseşte un unit cu proceduri asemănătoare celor din unit-ul CRT, unit pe care ne-am permis să-l numim MyCRT; el conţine o procedură Tone (asemănătoare lui Sound), care produce, folosind generatorul de tact 8253, sunete în difuzor; există şi o procedură de oprire a semnalului sonor, numită NoTone.

Avem şi cele două proceduri, deja cunoscute (vezi § 1.5), care acţionează asupra cursorului pâlpâitor, ascunzându-l (HideCursor), respectiv arătându-l (ShowCursor). Cea mai interesantă procedură din acest unit, numită PrintAt, realizează o scriere foarte rapidă pe ecran, direct în memoria video. De remarcat că este posibil, astfel, să se scrie şi în colţul dreapta-jos al ecranului, fără a se face scroll, cum se întâmplă dacă se foloseşte Write sau WriteLn.

unit MyCrt; interface { produce sunete clare şi rapide în PC-speaker (difuzor), folosind portul de sunet $61 } procedure Tone(freq: Word); { opreste difuzorul } procedure NoTone; { ascunde cursorul text } procedure HideCursor; { reafişează cursorul text } procedure ShowCursor;

Page 67: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

67

{ afişează rapid, direct în memoria video, un şir S, începând cu coloana x şi cu linia y, pe fondul ("background") b, de culoarea ("foreground") f } procedure PrintAt(x, y: byte; S: string; f, b: byte); implementation uses Crt; const sCntrl=$61; { portul pentru controlul sunetelor } soundOn=$03; { bit mask pentru a porni PC-speaker -ul } soundOff=$FC;{ bit mask pentru a opri PC-speaker -ul } C8253=$43; { adresa portului pentru a controla generatorul de tact 8253 } seTimer=$B6; { îi spune lui 8253 să aştepte urmatoarea frecvenţă } F8253=$42; { adresa frecvenţei în 8253 } procedure Tone(freq:word);assembler; asm MOV AL,$B6; OUT $43,AL; {scrie registrul de timer} MOV DX,$14; MOV AX,$4F38; DIV freq;{1331000/frecvenţa de puls} OUT $42,AL; MOV AL,AH; OUT $42,AL; {scrie în timer un byte la un moment} IN AL,$61; OR AL,3; OUT $61,AL {comută difuzorul pe "on"} end; procedure NoTone;assembler; asm IN AL,$61; AND AL,$FC; OUT $61,AL; end; procedure HideCursor; assembler; { se foloseşte întreruperea 10h } asm MOV AX,$0100; MOV CX,$2607; INT $10 end; procedure ShowCursor; assembler; asm MOV AX,$0100; MOV CX,$0506; INT $10 end; procedure PrintAt(x, y: byte; S: string; f, b: byte); type VideoLocation = record {formatul unei locaţii video pe doi bytes } VideoData: Char; { caracterul afişat } VideoAttribute: Byte; { atributele (culorile) } end; var i: Byte; VideoSegment: Word; { locaţia memoriei video } MonoSystem: Boolean; { mono vs. color } VidPtr: ^VideoLocation; { pointer la locaţii video } begin { Determină locaţia de memorie unde va fi tipărit şirul, în concordanţă cu tipul monitorului şi cu locaţia de pe ecran (coordonatele x si y). Apoi asociază pointerul VidPtr cu acea locaţie: VidPtr este un pointer către tipul VideoLocation. Inserează un caracter şi atributele sale, după care trece la noul caracter şi la noua

Page 68: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

68

locaţie video } MonoSystem := (LastMode in [0,2,7]); if MonoSystem then VideoSegment := $b000 else VideoSegment := $b800; VidPtr := Ptr(VideoSegment, 2*(80*(y - 1) + (x - 1))); for i := 1 to Length(S) do begin VidPtr^.VideoAttribute := (b shl 4) + f; { high = fundal; low = culoarea de scris } VidPtr^.VideoData := S[i]; { pune caracterul la locaţia respectivă } Inc(VidPtr); { trece la următoarea locaţie video } end end; end.

Orientat obiect, programul următor, este o modificare a programului Invadatorii, pe care l-am prezentat în cartea precizată1. Ca de obicei, programul cuprinde identificatori de tipuri, variabile, constante, proceduri şi funcţii cât mai sugestive, care, împreună cu câteva comentarii pe ici pe colo, fac programul să se prezinte de la sine. Scrieţi acest program în Turbo Pascal 7.0 (sau 6.0) şi rulaţi-l. Dacă vreţi să vă clarificaţi unele părţi din program, nu trebuie decât să-l rulaţi pas cu pas, sărind peste procedurile înţelese şi aşa mai departe, până înţelegeţi întregul program. Dacă nu, atunci căutaţi cartea despre care spuneam, în care “strămoşul” acestui program este foarte bine documentat. Modificaţi şi programul Invadatorii, folosind procedurile din unit-ul MyCRT, pentru a optimiza afişările şi sunetele.

program Navele; uses Crt, MyCrt; const kbUp=#72; kbDown=#80; kbLeft=#75; kbRight=#77; kbSpace=#32; kbAltS=#31; kbEsc=#27; kbF1=#59; Pauza1=50; Pauza2=10; type Str3 = String[3]; TNava = object x,y,dx,dy,cul: Byte; model: Str3; constructor Init(x0,y0: Byte; model0: Str3); procedure Display; procedure Clear; procedure Move; destructor Done; end; var Nava: array[1..20] of TNava; NrNave: Integer; type TOm = object c,nv:integer; { nv = nr. de vieţi } constructor Init(c0:integer); procedure Move; destructor Done; end; var Om:TOm;

1 Pătruţ, Bogdan - Grafică în OOP… şi nu numai, Editura ADIAS, Râmnicu Vâlcea, 1995

Page 69: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

69

type TGlont = object l,c:integer; constructor Init(l0,c0:integer); procedure Move; destructor Done; end; var Glont:TGlont; ExistaGlont, Sonor, Escape:boolean; i, Timp:Integer; sir: String; procedure Beeps; { sunete de mare efect se pot obţine, dacă se înlocuieşte acel 400 cu o valoare mai mare, de exemplu 1000 } var i: Integer; begin for i:=300 to 400 do begin Tone(10*i); Delay(5) end; NoTone end; constructor TNava.Init; begin x:=x0; y:=y0; model:=model0; dx:=Random(3)-1; dy:=Random(3)-1; cul:=9+Random(7) end; procedure TNava.Display; begin PrintAt(x-1,y,model,cul,Black) end; procedure TNava.Clear; begin PrintAt(x-1,y,' ',Black,Black) end; procedure TNava.Move; var i: Integer; begin Clear; Inc(x,dx); Inc(y,dy); if x<2 then x:=2; if x>78 then x:=78; if y<2 then y:=2; if y>20 then y:=20; Display; Delay(Pauza2); dx:=Random(3)-1; dy:=Random(3)-1 end; destructor TNava.Done; begin Clear end; constructor TOm.Init(c0:integer); begin c:=c0; PrintAt(c,21,'=³=',Yellow,Black) end; procedure TOm.Move; var comanda:char; begin if KeyPressed then begin Dec(Timp); comanda:=ReadKey; if comanda=kbEsc then Escape:=True else begin if comanda=kbSpace then if not ExistaGlont then begin

Page 70: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

70

Glont.Init(20,c+1); ExistaGlont:=True end else begin Glont.Done; Glont.Init(20,c+1) end; if comanda=#0 then begin comanda:=ReadKey; case comanda of kbAltS: Sonor:=not Sonor; kbLeft: begin PrintAt(c,21, ' ',Black,Black); Dec(c); if c<1 then c:=1; PrintAt(c,21,'=³=',Yellow, Black); end; kbRight: begin PrintAt(c,21, ' ',Black,Black); Inc(c); if c>78 then c:=78; PrintAt(c,21,'=³=',Yellow,Black); end end {case} end {if 2} end {else} end {if 1} end; destructor TOm.Done; begin PrintAt(c,21,' ',Black,Black) end; constructor TGlont.Init(l0,c0:integer); begin c:=c0; l:=l0; PrintAt(c,l,#24,LightRed,Black) end; procedure TGlont.Move; var i:integer; begin if ExistaGlont then begin PrintAt(c,l,' ',Black,Black); Dec(l); if l=0 then ExistaGlont:=False else begin PrintAt(c,l,#24,LightRed,Black); for i:=1 to NrNave do if (l=Nava[i].y) and (abs(c-Nava[i].x)<=1) then begin Beeps; PrintAt(Nava[i].x-1,Nava[i].y, ' ',Black,Black); Nava[i].y:=Nava[NrNave].y; Nava[i].x:=Nava[NrNave].x; Dec(NrNave) end end; end end; destructor TGlont.Done; begin PrintAt(c,l,' ',Black,Black) end; begin ClrScr; HideCursor; NrNave:=10; for i:=1 to NrNave do Nava[i].Init(Random(70)+5,Random(10)+5,'<=>'); Om.Init(15); ExistaGlont:=False; Timp:=1000; Escape:=False; PrintAt(20,23,'* NAVE * (P), (C) 1995 Bogdan Patrut', LightCyan,Blue);

Page 71: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

71

repeat Dec(Timp); Str(Timp,sir); PrintAt(25,24,'Timp: '+sir+' ',White,Black); Str(NrNave,sir); PrintAT(40,24,'Nave: '+sir+' ',White,Black); Delay(Pauza1); Om.Move; for i:=1 to NrNave do begin Nava[i].Move; Om.Move end; if ExistaGlont then Glont.Move; Until (NrNave=0) or (Timp=0) or Escape; Om.Done; for i:=1 to NrNave do Nava[i].Done; if ExistaGlont then Glont.Done; if Escape or (Timp=0) then PrintAt(25,10,'Joc terminat prin abandon ...', Yellow,Blue) else PrintAt(25,10,'Tu ai câştigat . Felicitări ! ', Black,White); Delay(2000) end.

Page 72: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

72

Page 73: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

73

Capitolul 6. Jocuri folosind unit-ul GRAPH

Noi plecăm de la premisa că cititorul este un tânăr (sau o tânără) iniţiată în programarea în limbajul Turbo Pascal şi care cunoaşte principalele proceduri şi funcţii grafice de care biblioteca (unit-ul) GRAPH dispune. De asemenea, presupunem că acel calculator pe care se lucrează este dotat cu o placă grafică (şi un monitor) VGA, deci nu trebuie decât să se ştie calea grafică unde se află fişierul EGAVGA.BGI (care este un driver grafic pentru Turbo Pascal), precum şi fişierele cu fonturi de caractere .CHR. Noi am considerat că aceste fişiere se află în directorul C:\TP\BGI, dar dacă la cititor nu este aşa, atunci el trebuie să rescrie procedurile de iniţializare grafică, sau să aducă fişierele respective în directorul curent. În continuare vom prezenta două jocuri care folosesc grafica în 16 culori a modului de lucru VGA (cu rezoluţia de 640 × 480 pixeli). În § 6.3 vom prezenta un program care editează nişte imagini, care apoi se pot transforma dintr-una în alta, ceea ce se numeşte morphing în literatura de specialitate.

6.1. Tetris Acesta e numele dat unei întregi clase de jocuri, cu cele mai diferite nume (Tetris, Tetris for Windows, Sextris, TetWin, Hextris, Pentix, PornTris), jocuri care toate au la bază o idee de provenienţă rusească, jocul clasic de Tetris. Avem o cutie verticală şi o serie de tetrominouri, adică piesele care pot fi formate prin alipirea, în toate modurile posibile, a patru pătrăţele. Alipirea este considerată doar pe laturi. Există 7 tipuri de piese, dacă se consideră şi oglindirile, nu însă şi rotirile. Acestea sunt prezentate în figură. Piesele, de unul din cele 7 tipuri (ales aleator), vin, câte una, în cutie prin partea sa superioară şi coboară, în fiecare moment, câte un rând. Jucătorul poate mişca piesa curentă în stânga sau în dreapta (cu tastele corespunzătoare de cursor) şi o poate roti, într-un sens, folosind o altă tastă (tasta de cursor sus). El trebuie să aşeze fiecare piesă în cutie cât mai bine, în sensul interpătrunderii cât mai mult a pieselor de la baza cutiei între ele. Dacă la un moment dat se realizează una sau mai multe linii orizontale pline, acestea sunt eliminate din cutie, liniile de deasupra lor coborând. Astfel, scopul jocului este de a păstra cât mai goală cutia, iar singura soluţie este de a aşeza cât mai bine piesele care vin. Folosind tasta de cursor jos se poate aduce direct la baza cutiei, piesa curentă peste celelalte piese, în cazul în care ne-am hotărât asupra poziţiei sale verticale. Jocul se termină doar când nu mai încap piese în cutie, dar am prevăzut şi o oprire forţată a jocului, prin apăsarea tastei Escape.

Page 74: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

74

Cele 7 tipuri de piese care apar în jocul de TETRIS Am implementat un simplu joc de Tetris în unit-ul uTet, în care JocDeTetris reprezintă procedura de joc propriu-zis. Parametrii săi de apel sunt: coordonatele colţului stânga-sus al cutiei, un număr întreg, până în 70, în funcţie de care se stabileşte viteza de joc, precum şi variabila scor, în care se comunică exteriorului rezultatul jocului, adică punctajul obţinut. Procedura salvează imaginea din zona dreptunghiulară care va fi ocupată de cutie, pe durata jocului, aşa încât vă puteţi folosi de această procedură într-un program propriu complex, pe care doriţi să-l înzestraţi şi cu un joc de amuzament. Important este să fiţi în modul grafic VGA, când apelaţi procedura JocDeTetris. Următorul program este o ilustrare a modului cum se poate folosi această procedură.

program Tetris; uses Graph, uTet; var scor: Integer; gd,gm,i: Integer; begin gd:=Detect; InitGraph(gd,gm,'C:\TP\BGI'); for i:=1 to 20 do begin SetColor(i); Rectangle(10*i,10*i,10*i+100,10*i+150) end; JocDeTetris(100,100,65,scor); ReadLn; CloseGraph; WriteLn('Ai obţinut ',scor,' puncte...'); ReadLn end.

Un moment din joc este vizualizat mai jos, unde este figurată doar cutia jocului, nu şi restul ecranului.

Page 75: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

75

Moment din desfăşurarea unui joc de TETRIS

Procedura JocDeTetris spuneam, se află în unit-ul uTet, unit pe care îl prezentăm în continuare, plin de comentarii. Dacă nu înţelegeţi ceva, atunci porniţi jocul şi rulaţi-l pas cu pas, sărind peste procedurile înţelese la un moment dat şi tot aşa, până înţelegeţi totul.

unit utet; interface uses Graph,Crt; procedure JocDeTetris(est,nord,NivelJoc: Integer; var scor: Integer); implementation const kbUp=#72; kbDwn=#80; kbLft=#75; kbRgt=#77; gol=0; plin=1; var Cutie: array[0..23,0..9] of integer; { matricea asociată cutiei } { coordonatele celor 4 componente ale piesei curente } x1,y1,x2,y2,x3,y3,x4,y4: Integer; { coordonatele viitoare ale celor 4 componente ale piesei } ax1,ax2,ax3,ax4,ay1,ay2,ay3,ay4: Integer; { numarul piesei curente (0..6), ipostaza în care se poate afla (0..maxim 3), precum şi culoarea ei } Piesa,Ipostaza,CuloarePiesa: Byte; { coordonatele colţului stânga-sus al cutiei } x00,y00: Integer; i,j: Byte; { indici } { linia curentă - se verifică dacă e plină sau nu } linia: Byte; { comanda dată de la tastatură: cursor <-, -> = deplasează piesa la stânga sau la dreapta; cursor sus = roteşte piesa, cursor jos = coboară pâna jos piesa, Escape = opreşte jocul }

Page 76: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

76

tasta: Char; { variabile necesare salvării/restaurării ecranului grafic, în porţiunea în care se desenează cutia jocului de Tetris } Po: Pointer; Size: Word; procedure SalveazaEcran; begin { Iau imaginea de la x00-15,y00-15,x00+117,y00+245 } Size:=ImageSize(x00-15,y00-15,x00+117,y00+245); GetMem(Po,Size); GetImage(x00-15,y00-15,x00+117,y00+245,Po^); SetViewPort(x00-15,y00-15,x00+117,y00+245, ClipOn); ClearViewPort; SetViewPort(0,0,GetMaxX,GetMaxY, ClipOn) end; procedure RestaureazaEcran; begin { se restaurează imaginea de pe ecran, dinainte de pornirea jocului de Tetris } PutImage(x00-15,y00-15,Po^,NormalPut); FreeMem(Po,Size) end; procedure DeseneazaPatrat(x,y,c: Integer); { desenează una din componentele piesei, în culoarea c; dacă c este Black, înseamnă că, de fapt, se şterge pătratul } begin SetColor(c); SetFillStyle(SolidFill,c); Bar(x00+10*x,10*y+y00,x00+10*x+10-1,10*y+10-1+y00); if c<>Black then begin SetColor(DarkGray); Rectangle(x00+10*x+1,y00+10*y+1, x00+10*x+10-2,y00+10*y+10-2) end end; procedure DeseneazaPiesa(c: Integer); { desenează piesa curentă, în culoarea c, care va fi fie CuloarePiesa, fie Black, caz în care de fapt se şterge piesa, pentru a fi reafişată în noua poziţie/ipostază } begin DeseneazaPatrat(x1,y1,c); DeseneazaPatrat(x2,y2,c); DeseneazaPatrat(x3,y3,c); DeseneazaPatrat(x4,y4,c) end; function SePoateMuta: Boolean; { testează dacă este posibilă o deplasare, după comanda sau conform coborârii obişnuite, a piesei curente } var SePoate: Boolean; begin SePoate:=true; if (ax1<0) or (ax2<0) or (ax3<0) or (ax4<0) or (ax1>9) or(ax2>9) or (ax3>9) or (ax4>9) then SePoate := False { depăşire limite cutie } else if (Cutie[ay1,ax1]=plin) or (Cutie[ay2,ax2]=plin) or (Cutie[ay3,ax3]=plin) or (Cutie[ay4,ax4]=plin) then

Page 77: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

77

SePoate := False; { într-una din noile coordonate cutia ar fi umplută } SePoateMuta:=SePoate end; function EstePlina(linia: Integer): Boolean; { testează dacă linia este plină cu alte piese, pentru a o elimina din cutie, a mări punctajul şi a actualiza liniile cutiei } var EPlina: Boolean; i: Byte; begin EPlina := True; i := 0; while (i<10) and EPlina do { se caută primul gol din linie, dacă acesta există } if Cutie[linia,i] = gol then EPlina:=false else Inc(i); EstePlina := EPlina end; function NuMaiEsteLoc: Boolean; begin NuMaiEsteLoc := (Cutie[y1,x1]=plin) or (Cutie[y2,x2]=plin) or (Cutie[y3,x3]=plin) or (Cutie[y4,x4]=plin) end; procedure ActualizeazaPozitiePiesa; { mută piesa curentă: o şterge din poziţia veche, îi determină poziţia nouă şi o desenează acolo } begin DeseneazaPiesa(Black); x1:=ax1; x2:=ax2; x3:=ax3; x4:=ax4; y1:=ay1; y2:=ay2; y3:=ay3; y4:=ay4; DeseneazaPiesa(CuloarePiesa) end; procedure MutaStinga; begin ax1:=x1-1; ax2:=x2-1; ax3:=x3-1; ax4:=x4-1; ay1:=y1; ay2:=y2; ay3:=y3; ay4:=y4; if SePoateMuta then ActualizeazaPozitiePiesa end; procedure MutaDreapta; begin ax1:=x1+1; ax2:=x2+1; ax3:=x3+1; ax4:=x4+1; ay1:=y1; ay2:=y2; ay3:=y3; ay4:=y4; if SePoateMuta then ActualizeazaPozitiePiesa end; procedure Coboara; begin ax1:=x1; ax2:=x2; ax3:=x3; ax4:=x4; ay1:=y1+1; ay2:=y2+1; ay3:=y3+1; ay4:=y4+1; if SePoateMuta then ActualizeazaPozitiePiesa

Page 78: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

78

end; procedure Roteste; { această procedură determină noua ipostază a piesei, în funcţie de ipostaza în care se află acum; astfel, pentru fiecare piesă în parte, pentru fiecare ipostază a piesei respective în parte s-a determinat viitoarea ipostază şi deci şi noile coordonate ale piesei; în figură este prezentat cazul piesei 0 (bara de 4 pătrăţele), care, în ipostaza 0 fiind (vertical), prin rotire trece în ipostaza 1 (orizontal) şi viceversa } begin case Piesa of 0: case Ipostaza of 0: begin ax1:=x2-1; ax2:=x2; ax3:=x2+1; ax4:=x2+2; ay1:=y2; ay2:=y2; ay3:=y2; ay4:=y2; Ipostaza:=1 end; 1: begin ax1:=x2; ax2:=x2; ax3:=x2; ax4:=x2; ay1:=y2-1; ay2:=y2; ay3:=y2+1; ay4:=y2+2; Ipostaza:=0 end; end; { 0 } 1: case Ipostaza of 0: begin ax1:=x3-1; ax2:=x3; ax3:=x3+1; ax4:=x3+1; ay1:=y3; ay2:=y3; ay3:=y3; ay4:=y3-1; Ipostaza:=1 end; 1: begin ax1:=x2; ax2:=x2; ax3:=x2; ax4:=x2-1; ay3:=y2-2; ay4:=y2-2; ay2:=y2-1; ay1:=y2; Ipostaza:=2 end; 2: begin ax3:=x3-1; ax4:=x3-1; ax2:=x3; ax1:=x3+1; ay3:=y3; ay2:=y3; ay1:=y3; ay4:=y3+1; Ipostaza:=3 end; 3: begin ax1:=x2; ax2:=x2; ax3:=x2; ax4:=x2+1; ay1:=y2; ay2:=y2+1; ay3:=y2+2; ay4:=y2+2; Ipostaza:=0 end; end; { 1 } 2: case Ipostaza of 0: begin ax1:=x2-1; ax2:=x2; ax3:=x2+1; ax4:=x2+1; ay1:=y2; ay2:=y2; ay3:=y2; ay4:=y2+1; Ipostaza:=1 end;

Page 79: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

79

1: begin ax1:=x1; ax2:=x1; ax3:=x1; ax4:=x1+1; ay3:=y1-1; ay4:=y1-1; ay1:=y1+1; ay2:=y1; Ipostaza:=2 end; 2: begin ax3:=x1; ax4:=x1; ax2:=x1+1; ax1:=x1+2; ay3:=y1; ay2:=y1; ay1:=y1; ay4:=y1-1; Ipostaza:=3 end; 3: begin ax1:=x2; ax2:=x2; ax3:=x2; ax4:=x2-1; ay1:=y2-2; ay2:=y2-1; ay3:=y2; y4:=y2; Ipostaza:=0 end end; { 2 } 3: case Ipostaza of 0: begin ax1:=x3; ax2:=x3+1; ax4:=x3+1; ax3:=x3+2; ay1:=y3; ay2:=y3; ay3:=y3; ay4:=y3-1; Ipostaza:=1 end; 1: begin ax1:=x3; ax2:=x3; ax3:=x3; ax4:=x3-1; ay1:=y3-2; ay2:=y3-1; ay4:=y3-1; ay3:=y3; Ipostaza:=2 end; 2: begin ax1:=x4-1; ax2:=x4; ax4:=x4; ax3:=x4+1; ay1:=y4; ay2:=y4; ay3:=y4; ay4:=y4+1; Ipostaza:=3 end; 3: begin ax1:=x1; ax2:=x1; ax3:=x1; ax4:=x1+1; ay1:=y1-1; ay2:=y1; ay4:=y1; ay3:=y1+1; Ipostaza:=0 end; end; { 3 } 4: begin {piesa pătrată, care oricum ar fi rotită, rămâne tot ea} ax1:=x1; ax2:=x1; ax3:=x3; ax4:=x3; ay1:=y1; ay3:=y1; ay2:=y2; ay4:=y2 end; { 4} 5: case Ipostaza of 0: begin ax1:=x2; ax2:=x2; ax3:=x2+1; ax4:=x2+1; ay4:=y2-1; ay2:=y2; ay3:=y2; ay1:=y2+1; Ipostaza:=1 end; 1: begin ax1:=x2-1; ax2:=x2; ax3:=x2; ax4:=x2+1; ay1:=y2; ay2:=y2; ay3:=y2+1; ay4:=y2+1; Ipostaza:=0 end; end; { 5 } 6: case Ipostaza of

Page 80: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

80

0: begin ax1:=x3-1; ax2:=x3-1; ax3:=x3; ax4:=x3; ay1:=y3-1; ay2:=y3; ay3:=y3; ay4:=y3+1; Ipostaza:=1 end; 1: begin ax1:=x3-1; ax2:=x3; ax3:=x3; ax4:=x3+1; ay3:=y3; ay4:=y3; ay1:=y3+1; ay2:=y3+1; Ipostaza:=0 end end { 6 } end; { case Piesa } if SePoateMuta then ActualizeazaPozitiePiesa end; { Roteste } procedure AlegeNouaPiesa; begin { se alege o piesă şi o culoare (deschisă) pentru ea } Piesa := Random(7); CuloarePiesa := Random(7)+8; { se determină coordonatele iniţiale (în vârful cutiei) ale celor 4 pătrate componente ale piesei, în funcţie de piesă; pentru toate piesele se consideră prima ipostază (0) } Ipostaza:=0; case Piesa of 0: begin x1:=4; x2:=4; x3:=4; x4:=4; y1:=0; y2:=1; y3:=2; y4:=3 end; 1: begin x1:=4; x2:=4; x3:=4; x4:=5; y1:=0; y2:=1; y3:=2; y4:=2 end; 2: begin x1:=5; x2:=5; x3:=5; x4:=4; y1:=0; y2:=1; y3:=2; y4:=2 end; 3: begin x1:=4; x2:=4; x3:=4; x4:=5; y1:=0; y2:=1; y4:=1; y3:=2 end; 4: begin x1:=4; x2:=4; x3:=5; x4:=5; y1:=0; y3:=0; y2:=1; y4:=1 end; 5: begin x1:=4; x2:=5; x3:=5; x4:=6; y1:=0; y2:=0; y3:=1; y4:=1 end; 6: begin x1:=4; x2:=5; x3:=5; x4:=6; y3:=0; y4:=0; y1:=1; y2:=1 end; end; end; procedure ActioneazaDupaComanda; begin { se citeşte o tastă de cursor }

Page 81: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

81

while KeyPressed do Tasta := ReadKey; case tasta of kbLft : MutaStinga; kbRgt : MutaDreapta; kbUp : Roteste; kbDwn : repeat { coborâre automată a piesei ! } Coboara; until not SePoateMuta end end; procedure JocDeTetris(est,nord,NivelJoc: Integer; var scor: Integer); { ... joc de Tetris ...} procedure DeseneazaCutia; begin { desenez cutia } SetColor(White); Rectangle(x00-15,y00-15,x00+117,y00+245); SetFillStyle(SolidFill,Cyan); Bar(est-10,nord,est-2,nord+240); Bar(est+102,nord,est+112,nord+240); Bar(est,nord+232,est+100,nord+240); OutTextXY(est+20,nord+233,' TETRIS ') end; procedure InitializeazaCutia; begin { iniţializare cutie: la început ea e goală ... } for i:=0 to 22 do for j:=0 to 9 do Cutie[i,j] := gol; { consider baza cutiei, exterioară, dar plină, pentru a verifica mai uşor când vine o piesă şi trebuie să se oprească jos ... } for i:=0 to 9 do Cutie[23,i] := plin end; procedure DisplayScor; var SirScor: String[5]; begin Str(scor,SirScor); SetColor(GetBkColor); OutTextXY(est+3,nord-10,'Puncte:ÛÛÛÛÛÛÛ'); SetColor(White); OutTextXY(est+3,nord-10,'Puncte:'+sirscor); end; procedure ActualizeazaScor; begin { scorul depinde de piesa aleasă; el este mai mic pentru piesele simple: bara şi pătratul } if Piesa in [1,2,5,6] then scor:=scor+2*(70-NivelJoc) else scor:=scor+70-NivelJoc;

Page 82: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

82

DisplayScor end; procedure EliminaLiniilePline; begin { se verifică dacă sunt linii pline şi care sunt acelea; fiecare linie plină este eliminată din cutie } linia := 22; { ultima linie } while linia > 2 do begin if EstePlina(linia) then begin Sound(300); Delay(100); NoSound; scor := scor+10*(70-NivelJoc); DisplayScor; { urmează actualizarea cutiei, obţinută după eliminarea liniei pline } for i:=linia downto 1 do for j:=0 to 9 do begin Cutie[i,j]:=Cutie[i-1,j]; if Cutie[i,j]=1 then DeseneazaPatrat(j,i,LightCyan) else DeseneazaPatrat(j,i,Black); end; for i:=0 to 9 do Cutie[0,i]:=gol; end { EstePlina(lin) } else Dec(linia) end { while lin>2 } end; begin { JocDeTetris } x00:=est; y00:=nord; SalveazaEcran; DeseneazaCutia; InitializeazaCutia; scor:=0; Randomize; { începe jocul ! } repeat AlegeNouaPiesa; ActualizeazaScor; { Când nu mai încap piese în cutie, deci ultima piesă sosită nu mai poate intra, înseamnă că jocul a luat sfârşit } if NuMaiEsteLoc then begin RestaureazaEcran; Exit end; { altfel, înseamnă că piesa poate intra în cutie, deci se va desena } DeseneazaPiesa(CuloarePiesa); repeat Delay((NivelJoc-48)*30); if KeyPressed then begin { se preia o comandă de la tastatură } Tasta := ReadKey; if Tasta = #27 { Escape } then { terminare joc prin abandon } begin RestaureazaEcran; Exit end else ActioneazaDupaComanda end;

Page 83: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

83

{ piesa coboară câte o linie, continuu } Coboara until not SePoateMuta; { acum piesa nu se mai poate muta, deci a ajuns la baza cutiei, între celelate piese } { se precizează că în cutie s-a pus şi această piesă ... } Cutie[y1,x1] := plin; Cutie[y2,x2] := plin; Cutie[y3,x3] := plin; Cutie[y4,x4] := plin; EliminaLiniilePline { şi se actualizează şi scorul ... } until False end; { JocDeTetris } end.

6.2. Feţe

Oarecum asemănător Tetris-ului, jocul pe care vi-l propunem în continuare are următorul scop: de a păstra o cutie cât mai goală. Cutia este văzută ca un set de patru turnuri verticale, turnuri care pot fi umplute cu nişte piese dreptunghiulare, fiecare piesă fiind una din cele trei părţi ale unei imagini. Numărul de imagini este de patru, iar imaginile reprezintă chipurile a patru fete, aşa încât am putea numi jocul şi Fete. Aceste bucăţi de feţe vin din partea superioară a ecranului (a cutiei) şi coboară pînă la vîrful unui turn, asemănător jocului Tetris. Însă noi putem deplasa, în timp ce se mişcă în jos, aceste piese, cu ajutorul tastelor Q = stânga şi P = dreapta. De asemenea, apăsând spaţiu, putem să schimbăm imaginea de pe piesă. Dacă la începutul jocului se doreşte ca schimbarea să fie totală, atunci, la apăsarea tastei de spaţiu imaginea de pe piesa curentă va fi înlocuită cu una din cele 12 imagini posibile; (numărul este dat de produsul: 3 imagini pe piesă × 4 piese (feţe)). Dacă nu se doreşte acest lucru, care de altfel ar îngreuna jocul, atunci apăsarea tastei spaţiu va permuta circular cele trei imagini din cadrul aceleiaşi feţe. Jucătorul va urmări să poziţioneze câte trei piese formând o aceeaşi faţă, pe un turn, lucru care va determina eliminarea acelei feţe de pe ecran. La fiecare trei piese eliminate, programul va elimina piesele din vârfurile celor trei turnuri, lucru care, în general, este avantajos. Un moment din desfăşurarea unui joc de Feţe este prezentat în figura următoare.

Aspect din jocul FEŢE

Page 84: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

84

În (de acum) bine cunoscutul nostru stil de editare a programelor, în paginile ce urmează aveţi listat programul Feţe. În text am pus în evidenţă trei proceduri generale, care vor putea fi grupate de dumneavoastră într-un unit special de grafică şi vor putea fi folosite şi mai târziu, cu alte ocazii: Acestea sunt: 1. procedure GetName(mesaj: String; var nume: String); - procedura afişează un mesaj, în centrul ecranului, într-un dreptunghi şi aşteaptă introducerea unui şir de caractere (de lungime maximă 40), care este preluat în variabila nume; procedura poate fi utilă şi când se doreşte citirea unor valori numerice (întregi sau reale), dacă ulterior acest nume va fi convertit în număr cu procedura Val . 2. procedure Message(mesaj: String); - procedura afişează un mesaj într-un dreptunghi din centrul ecranului şi aşteaptă acţionarea unei taste; 3. procedure LoadImage(x,y: Integer; fis:String); - procedura încarcă o imagine recunoscută de limbajul Turbo Pascal, deci o imagine preluată de pe ecran cu GetImage, care a fost salvată cu o procedură de genul celei folosite în capitolul4. De remarcat că pentru a putea lucra cu acest program trebuie să dispuneţi de cele patru fişiere de imagine, care ar putea fi obţinute în ultimă instanţă din nişte BMP-uri de care dispuneţi, folosind programul BMP2IMA din capitolul 4, capitol pe care trebuie nepărat să-l citiţi, pentru a nu avea surprize neplăcute. Imaginile din fişierele cu extensia IMA din program (L_DANA, L_IRINA, L_ANA, L_ELIZA) au dimensiunile: 88 × 114 . Ele folosesc aceeaşi paletă de culori, care poate fi încărcată din fişierul bitmap L_DANA.BMP. Aşadar, dacă vreţi să vă faceţi propriile imagini .IMA, trebuie să aveţi grijă să păstraţi dimensiunile, sau, dacă nu, va trebui să modificaţi următoarele constante, în funcţie de necesităţile dumneavoastră, necesităţi dictate de fişierele cu care lucraţi: lat = 88 = lăţimea unei piese, deci şi a unei imagini întregi; inalt = 38 = înălţimea unei piese, adică a treia parte din înălţimea unei imagini (114); SizeOfImage = 22182 = mărimea unei imagini (dată de ImageSize) Puteţi să modificaţi şi numărul de turnuri (NrTurnuri) pe care se pot aşeza piesele în cădere, precum şi numărul de linii ale unui turn (NrLinii). Dacă dispuneţi de mai multe sau mai puţine imagini, va trebui să modificaţi constanta corespunzătoare (NrFete). Iată textul sursă al programului:

program FeteDeOameni; uses Crt, Graph, Graph1{, ViewBMP}; label sfirsit; const lat=88; inalt=38; NrTurnuri=4; NrFete=4; NrLinii=12; SizeOfImage=22182; type pereche=record unu,doi: Byte end; var top: array[1..NrTurnuri] of Byte; turn: array[1..NrTurnuri,1..NrLinii] of pereche; ii: Byte; Size: Word; pauza, cod: Integer; raspuns: String; premiu: Integer; sir: String; dorinta: Boolean; Xmij, Ymij: Integer;

Page 85: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

85

const NumeFata:array[1..NrFete] of String=('L_DANA','L_IRINA','L_ANA','L_ELIZA'); { numele fisierelor cu imagini } var piesa: pereche; lin,poz,i,j: Byte; tasta: Char; fata: array[1..NrFete,1..3] of Pointer; { 3 fragemente } procedure ClearView(x1,y1,x2,y2: Integer); begin SetViewPort(x1,y1,x2,y2,ClipOn); ClearViewPort; SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn) end; procedure Message(mesaj: String); var Size: Word; l,b,x,y: Integer; P: Pointer; begin { afisez un mesaj de atentionare si astept apasare buton mouse } l:=TextWidth(mesaj) div 2 + 5; Size:=ImageSize(Xmij-l,Ymij-10,Xmij+l,Ymij+10); GetMem(P,Size); GetImage(Xmij-l,Ymij-10,Xmij+l,Ymij+10,P^); ClearView(Xmij-l,Ymij-10,Xmij+l,Ymij+10); Rectangle(Xmij-l,Ymij-10,Xmij+l,Ymij+10); SetTextJustify(CenterText,CenterText); OutTextXY(Xmij,Ymij,mesaj); SetTextJustify(LeftText,TopText); Tasta := ReadKey; Delay(300); PutImage(Xmij-l,Ymij-10,P^,NormalPut); FreeMem(P,Size) end; procedure GetName(mesaj: String; var nume: String); var Size: Word; l,b,x,y: Integer; P: Pointer; tasta: Char; begin { afişez mesajul, apoi } { citesc de la tastatură nume } l:=TextWidth('����������������������������������������') div 2 + 5; { caracterul cu codul 219 } Size:=ImageSize(Xmij-l,Ymij-10,Xmij+l,Ymij+14); GetMem(P,Size); GetImage(Xmij-l,Ymij-10,Xmij+l,Ymij+14,P^); ClearView(Xmij-l,Ymij-10,Xmij+l,Ymij+14); Rectangle(Xmij-l,Ymij-10,Xmij+l,Ymij+14); SetTextJustify(CenterText,CenterText); OutTextXY(Xmij-8,Ymij,mesaj); nume:=''; repeat tasta:=ReadKey; if tasta in ['0'..'9','a'..'z','A'..'Z',' ','-'] then begin nume:=nume+tasta; SetColor(GetBkColor); OutTextXY(Xmij,Ymij+8, { caracterul #219 }

Page 86: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

86

'����������������������������������������'); SetColor(White); OutTextXY(Xmij,Ymij+8,nume) end else if tasta=#8 then begin nume:=Copy(nume,1,Pred(Length(nume))); SetColor(GetBkColor); OutTextXY(Xmij,Ymij+8,{caracterul cu codul 219} '����������������������������������������'); SetColor(White); OutTextXY(Xmij,Ymij+8,nume) end until (tasta = #13) or (Length(nume)=40); Delay(300); SetTextJustify(LeftText,TopText); PutImage(Xmij-l,Ymij-10,P^,NormalPut); FreeMem(P,Size) end; function StopJoc: Boolean; var SJ: Boolean; i,j: Byte; begin SJ:=False; i:=1; while (not SJ) and (i<=NrTurnuri) do begin if top[i]>=3 then { 3 - nr. de fragmente } begin if (turn[i,top[i]].doi=1) and (turn[i,top[i]-1].doi=2) and (turn[i,top[i]-2].doi=3) and (turn[i,top[i]].unu=turn[i,top[i]-1].unu) and (turn[i,top[i]].unu=turn[i,top[i]-2].unu) then SJ:=True end; i:=i+1 end; ii:=i-1; if not SJ then ii:=0; StopJoc:=SJ end; function Umplere: Boolean; var i: Byte; gasit: Boolean; begin i:=1; gasit:=False; while (i<=NrTurnuri) and (not gasit) do if NrLinii-1 = top[i] then gasit:=True else Inc(i); Umplere:=gasit end; procedure InitTabla; var i,j: Byte; begin

Page 87: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

87

for i:=1 to NrTurnuri do begin top[i]:=0; for j:=1 to NrLinii do begin turn[i,j].unu:=0; turn[i,j].doi:=0 end end end; procedure Scrie(i,j: Byte; t: pereche); begin PutImage((lat + lat div 2)*(i-1),GetMaxY-inalt*j, Fata[t.unu,t.doi]^,CopyPut) end; procedure Spatiu(i,j: Byte); begin ClearView((lat + lat div 2)*(i-1),GetMaxY-inalt*j, (lat + lat div 2)*i-5,GetMaxY-inalt*(j-1)); turn[i,j].unu:=0; turn[i,j].doi:=0 end; procedure DesTabla; var i,j: Byte; begin for i:=1 to NrTurnuri do for j:=1 to top[i] do Scrie(i,j,turn[i,j]) end; procedure LoadImage(x,y: Integer; fis:String); var P:pointer; f:file; control:Integer; begin {incarc imagine .IMA} fis:=fis+'.IMA'; GetMem(P,SizeOfImage); Assign(f,fis); {$I-}Reset(f);{$I+} if IOResult = 0 then begin BlockRead(f,P^,(SizeOfImage Div 128)+1,control); Close(f); PutImage(x,y,P^,NormalPut); FreeMem(P,SizeOfImage) end else Message('Imagine negasită...') end; procedure PreiaImagine(x1,y1,x2,y2: Integer; var Imagine: Pointer); begin Size:=ImageSize(x1,y1,x2,y2); GetMem(Imagine,Size); GetImage(x1,y1,x2,y2,Imagine^) end;

Page 88: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

88

procedure ElibMemorie; var i,j: Byte; begin for j:=1 to NrFete do for i:=1 to 3 do FreeMem(fata[j,i],Size); end; procedure TitluJoc; begin SetColor(Random(16)); OutTextXY(600,50,'F'); OutTextXY(600,100,'E'); OutTextXY(600,150,'T'); OutTextXY(600,200,'E'); OutTextXY(600,160,','); SetColor(White) end; begin { Program principal ... } repeat premiu:=0; OpenGraph; Xmij:=GetMaxX div 2; Ymij := GetMaxY div 2; SetTextJustify(CenterText,CenterText); SetTextStyle(DefaultFont,HorizDir,5); OutTextXY(320,50,'FETE'); SetTextStyle(DefaultFont,HorizDir,2); OutTextXY(320,80,'(P),(C) 1995 Bogdan PATRUT'); SetTextStyle(DefaultFont,HorizDir,1); OutTextXY(320,120,'incearcă să restaurezi măcar una din feţe'); OutTextXY(320,135,'Q = stânga, P = dreapta, Space = schimbă, Esc = stop'); OutTextXY(320,400,'S = START JOC'); { dacă se doreşte încărcarea paletei iniţiale de culori } { LoadPackBMPFile(100,200,'L_DANA.BMP'); şi se pune şi uses ViewBMP, la început, deci se scot acoladele şi de aici şi de acolo } for j:=1 to NrFete do for i:=0 to 2 do { 2-0 + 1 = 3 = nr. de fragmente ! } begin LoadImage(100*j,200,NumeFata[j]); PreiaImagine(100*j,200+inalt*i, 100*j+lat,200+inalt*(i+1),fata[j,i+1]) end; repeat tasta:=ReadKey until UpCase(tasta) ='S'; repeat GetName('Daţi nivelul de joc [1..5] !',sir); Val(sir,pauza,cod) until cod=0; pauza:=700-100*pauza; GetName('Doriţi schimbare totală ? [d/n]',sir); dorinta:=sir[1] in ['d','D']; InitTabla; DesTabla; Randomize; ClearDevice; SetTextStyle(DefaultFont,HorizDir,1); OutTextXY(560,300,'(C) 1995'); OutTextXY(560,315,'B. Pătruţ');

Page 89: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

89

OutTextXY(560,310,' Î'); OutTextXY(560,320,' ,'); SetTextStyle(DefaultFont,HorizDir,3); repeat TitluJoc; piesa.unu:=Random(NrFete)+1; piesa.doi:=Random(3)+1; { 3 fragmente } poz:=Random(NrTurnuri)+1; lin:=NrLinii; repeat TitluJoc; Delay(pauza); Spatiu(poz,lin); lin:=lin-1; if KeyPressed then begin tasta:=ReadKey; case tasta of 'q': if (poz>1) and (top[poz-1]<lin) then poz:=poz-1; 'p': if (poz<NrTurnuri) and (top[poz+1]<lin) then poz:=poz+1; ' ': begin if dorinta then {schimbare totală} piesa.unu:=Random(NrFete)+1; Inc(piesa.doi); if piesa.doi=4 then piesa.doi:=1; { 3 fragmente } Scrie(poz,lin,piesa) end; #27: begin ElibMemorie; CloseGraph; GoTo sfirsit end end; if tasta in ['p','q'] then Spatiu(poz,lin) end; Scrie(poz,lin,piesa); until top[poz]=lin-1; Sound(100); Delay(30); NoSound; top[poz]:=top[poz]+1; turn[poz,top[poz]]:=piesa; if StopJoc then begin Spatiu(ii,top[ii]); Spatiu(ii,top[ii]-1); Spatiu(ii,top[ii]-2); top[ii]:=top[ii]-3; Inc(premiu); if premiu mod 3 = 0 then for i:=1 to NrTurnuri do if top[i]>0 then begin Spatiu(i,top[i]); Dec(top[i]) end end; until Umplere; SetTextStyle(DefaultFont,HorizDir,3); SetTextJustify(CenterText,CenterText);

Page 90: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

90

if premiu >= 7 then begin OutTextXY(320,50,'Aţi făcut o treabă bună !'); DesTabla; for i:=1 to 10 do begin Sound(100+20*i); Delay(30) end; NoSound; ElibMemorie; ClearDevice end else begin ElibMemorie; OutTextXY(320,50,'Joc terminat ! ...'); for i:=10 downto 1 do begin Sound(100+20*i); Delay(50) end; NoSound end; tasta:=ReadKey; CloseGraph; sfirsit: GotoXY(30,13); Write('Jucaţi "FEŢE" !!! '); GoToXY(30,14); Write(' '' '); GoToXY(30,15); Write('Alt joc ? [d/n] > '); ReadLn(raspuns) until UpCase(raspuns[1])='N' end.

Părăsirea jocului se poate face în orice moment, dacă se acţionează tasta Escape. Ar fi de dorit să vă obţineţi nişte imagini pentru a putea să vă distraţi cu acest joc, dar dacă totuşi nu reuşiţi, atunci scrieţi autorilor, care vă vor oferi fişierele originale.

* E bine să exersaţi în a obţine bitmap-uri, folosind programe de grafică de gen, cum ar fi Pizzaz Plus, Grab, VPIC, Paint Brush sau Corel Photo Paint. Bitmap-urile împachetate pot fi folosite la jocuri precum acesta, iar cele despachetate (256 culori) în jocuri precum cele ce urmează în cuprinsul acestei cărţi.

7.3. Transformări de imagini

Mai puţin având de a face cu jocurile, nu însă şi cu joaca, am putea spune, programele pe care urmează să vi le prezentăm şi explicăm în continuare sunt încercări timide de morphing. Prin morphing se înţelege transformarea unei imagini date în altă imagine. Algoritmii de morphing sunt dintre cei mai sofisticaţi, mai ales în cazul în care imaginile sunt memorate sub forma unor hărţi de biţi, deoarece trebuie mai întâi să se realizeze nişte recunoaşteri de forme, lucru nu tocmai uşor. În cazul nostru, nu vom avea de a face cu desene reprezentate sub forma de hărţi de biţi, ci de desene văzute ca o colecţie de suprafeţe de diferite culori. O suprafaţă este, de fapt o curbă, reprezentată de o linie poligonală, care are o anumită culoare şi care eventual poate fi umplută în respectiva culoare. Să zicem că avem două astfel de desene, care au acelaşi număr de suprafeţe, suprafeţele corespunzătoare având acelaşi număr de puncte, eventual aceleaşi culori şi acelaşi stil de umplere

Page 91: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

91

(da sau nu). Transformarea primului desen în cel de-al doilea presupune transformarea fiecărei suprafeţe (deci linii poligonale sau curbe) a primului desen în cea corespunzătoare din cel de al doilea desen.

Acest lucru este realizat de procedura TransfPic, din unit-ul uAnim, pe care îl listăm în

paginile ce urmează. El cuprinde şi alte proceduri de lucru cu desene (editare, modificare a unui desen, salvare, restaurare, etc) şi este bine comentat, încât nu credem că veţi avea probleme în înţelegerea lui. Unit-ul foloseşte unit-ul uMouse de lucru cu mouse-ul, pe care îl găsiţi în anexă, la sfârşitul cărţii.

unit uAnim; { în cele ce urmează vom considera sinonime expresiile "suprafaţă", "curbă" şi "linie poligonală" } interface type YesNo = (no,yes,soso); { stilul de desenare a unei suprafeţe sau a unui desen: no = doar "curba" (linia poligonală), în alb; soso = doar "curba", în culoarea ei; yes = şi curba şi conţinutul ei, în culoarea respectivă } type surface=record { o suprafaţă este o linie poligonală, având un număr de puncte, fiecare punct fiind memorat prin cele două coordonate ale sale: pentru o suprafaţă S, prin S[3,2] vom înţelege coordonata a II-a(deci y) a celui de-al treilea punct } Pct:array[1..30,1..2] of Integer; NrPct: Byte; { numărul de puncte } Fil,Cul: Byte { Fil = 0 (neumplut) sau 1 (umplut } { Cul = 0..15 = culoarea suprafeţei } end; desen=record {un desen este o mulţime de NrSurf suprafeţe} Surf: array[1..20] of surface; {pentru un desen D, prin Surf[2] vom înţelege cea de a doua linie poligonală (suprafaţă) ce îl compune } NrSurf: Byte end; procedure OpenGraph; { iniţializează modul grafic EGA, sau VGA mediu, cu 16 culori şi două pagini video, care pot fi interschimbate, pentru a crea animaţie } procedure CreatePic(var D: desen); { crează interactiv un desen D, folosind mouse-ul } procedure DrawPic(D: desen; stil: YesNo); { afişează un desen D, în stilul stil } procedure TransfPic(D1, D2: desen; n: Byte; stil: YesNo); { transformă, prin translaţie de puncte, desenul D1 în desenul D2; transformarea este realizată în n paşi, iar desenările se fac în stilul precizat } procedure ModifyPic(var D: desen);

Page 92: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

92

{ modifică un desen D, în mod interactiv, prin modificarea, pe rând a tuturor suprafeţelor ce îl compun } procedure SavePic(D: desen; fis: String); { salvează un desen D într-un fişier fis } procedure LoadPic(var D: desen; fis: String); { încarcă dintr-un fişier fis un desen, în variabila D } var PauzaDeAnimatie: Integer; { necesară lui TransfPic } implementation uses Graph, uMouse, Crt; var pag: Byte; { numărul paginii curente ce se vizualizează } procedure Beep; { avertizare sonoră } begin Sound(200); Delay(100); NoSound end; function Apartine(x,y,x1,y1,x2,y2: Integer): Boolean; { verifică apartenenţa unui punct (x,y) la un dreptunghi (x1,y1)-(x2,y2) } begin Apartine := (x1<=x) and (x<=x2) and (y1<=y) and (y<=y2) end; procedure OpenGraph; { initializare grafică } var gd,gm: Integer; begin gd:=VGA; gm:=VGAmed; InitGraph(gd,gm,'C:\TP\BGI'); { calea grafică către fişierele BGI } pag:=0; end; procedure Bara; { şterge o porţiune din partea stângă sus a ecranului, loc unde se fac unele citiri în cadrul procedurilor de creare şi modificare de desen } begin SetViewPort(0,0,150,60,ClipOn); ClearViewPort; SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn) end; procedure DrawSurface(S: surface; stil: YesNo); { desenează o suprafaţă S, în stilul stil } var cc: Byte; begin cc:=GetColor; { culoarea dinaintea apelului procedurii } case stil of yes: begin { dacă este stilul "yes", atunci se desenează şi linia poligonală } SetColor(S.Cul); DrawPoly(S.NrPct, S.Pct); { dar se umple şi poligonul } SetFillStyle(S.Fil, S.Cul); FillPoly(S.NrPct, S.Pct) end; no: begin { desenare simplă cu alb } SetColor(White);

Page 93: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

93

DrawPoly(S.NrPct, S.Pct) end; soso: begin { desenare simplă, în culoarea proprie } SetColor(S.Cul); DrawPoly(S.NrPct, S.Pct) end end; SetColor(cc) { restaurarea culorii dinaintea apelului procedurii } end; procedure CreateSurface(var S: surface); { această procedură creează interactiv, cu ajutorul mouse-ului, o linie poligonală S, de culoare S.Cul, care poate fi umplută (S.Fil=1) sau nu (S.Fil=0) } var b,x1,y1,x2,y2,xs,ys: Integer; begin S.NrPct:=1; { se aşteaptă fixarea primului punct al lui S } repeat MouseData(b,xs,ys) until b = 1; x1:=xs; y1:=ys; S.Pct[S.NrPct,1]:=xs; S.Pct[S.NrPct,2]:=ys; { se memorează coordonatele acestui prim punct } repeat SetWriteMode(1); { se alege modul de scriere XorPut } MouseHide; repeat { se uneşte penultimul punct (x1,y1) cu cel actual, care se determină cu mouse-ul, prin apasarea unui buton; observaţi cum se desenează liniile intermediare, prin două apeluri ale lui Line, în modul de scriere XorPut } MouseData(b,x2,y2); Line(x1,y1,x2,y2); MouseShow; Delay(50); MouseHide; { se afişează puţin mouse-ul, pentru a vedea unde ne aflăm } Line(x1,y1,x2,y2); until b in [1,2]; SetWriteMode(0); { se revine la modul normal de scriere grafică } if b=1 then begin {apăsarea butonului din stânga înseamna un nou punct} Delay(300); Inc(S.NrPct); S.Pct[S.NrPct,1]:=x2; S.Pct[S.NrPct,2]:=y2; { se memorează coordonatele acestui nou punct, apoi se desenează noua componentă a liniei poligonale } Line(x1,y1,x2,y2); MouseShow; x1:=x2; y1:=y2 end until b = 2; { apăsarea butonului din dreapta al mouse-ului încetează desenarea suprafeţei, adică a liniei poligonale } Beep; {un zgomot de avertizare că desenarea s-a sfârşit} MouseHide; Bara; { în continuare se introduc date referitoare

Page 94: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

94

la tipul suprafeţei } OutTextXY(10,0,'Solid=1/Empty=0 ? '); { este umplută sau nu } GoToXY(3,2); ReadLn(S.Fil); OutTextXY(10,27,'Culoare ? '); { ce culoare are } GoToXY(3,4);ReadLn(S.Cul); MouseShow; DrawSurface(S,yes); { o desenăm, în stilul "yes" } SetColor(White); MouseShow end; procedure ModifySurface(var S: surface); { aceasta procedură permite modificarea unei suprafeţe S, utilizând mouse-ul; modificarea se face prin "agăţarea" diferitelor puncte ale suprafeţei şi modificarea poziţiei lor; acest fapt va duce şi la modificarea liniilor în care intră acest punct, ca extremitate } const eps=3; {precizia in pixeli, pentru agăţarea cu mouse-ul a punctelor} var b,x,y,i,x_1,y_1,x_2,y_2: Integer; { b=butonul de mouse; (x,y)=coordonatele mouse-ului (în preajma /) punctului agăţat, (x_1,y_1) si (x_2,y_2) sunt punctele vecine celui agăţat } gasit: Boolean; { s-a agăţat un punct sau nu ? } begin SetColor(White); repeat MouseData(b,x,y); i:=1; gasit:=False; { se cauta dacă s-a agăţat un punct sau nu } while (i<=S.NrPct) and (not gasit) do if (b=1) and Apartine(x,y,S.Pct[i,1]-eps,S.Pct[i,2]-eps, S.Pct[i,1]+eps,S.Pct[i,2]+eps) then begin gasit:=True; { da s-a agăţat punctul al i-lea, deci coordonatele sale sunt: S.Pct[i,1] si S.Pct[i,2] } Beep; { se semnalizează sonor acest lucru } SetWriteMode(1); MouseHide; Delay(300); {se calculează coordonatele vecinilor lui} if i<S.NrPct then begin x_2:=S.Pct[i+1,1]; y_2:=S.Pct[i+1,2] end else begin x_2:=-1; y_2:=-1 end; if i>1 then begin x_1:=S.Pct[i-1,1]; y_1:=S.Pct[i-1,2] end else begin x_1:=-1; y_1:=-1 end; { se trasează liniile între punctul găsit şi vecinii săi} if x_1+y_1<>-2 then

Page 95: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

95

Line(x_1,y_1,S.Pct[i,1],S.Pct[i,2]); if x_2+y_2<>-2 then Line(S.Pct[i,1],S.Pct[i,2],x_2,y_2); if (x_1+y_1<>-2) or (x_2+y_2<>-2) then Line(S.Pct[i,1],S.Pct[i,2], S.Pct[i,1],S.Pct[i,2]); repeat { se stabilesc alte coordonate (x,y) pentru punctul i } MouseData(b,x,y); S.Pct[i,1]:=x; S.Pct[i,2]:=y; { tot aşa, se desenează liniile între punctul i, care acum va avea coordonatele (x,y) ale mouse-ului, şi vecinii săi (x_1,y_1), respectiv (x_2,y_2), dacă aceştia există (lucru controlat prin suma coordonatelor <> -2 } if x_1+y_1<>-2 then Line(x_1,y_1,x,y); if x_2+y_2<>-2 then Line(x,y,x_2,y_2); MouseShow; Delay(50); MouseHide; if x_1+y_1<>-2 then Line(x_1,y_1,x,y); if x_2+y_2<>-2 then Line(x,y,x_2,y_2) until b=1; SetWriteMode(0); if x_1+y_1<>-2 then Line(x_1,y_1,x,y); if x_2+y_2<>-2 then Line(x,y,x_2,y_2); S.Pct[i,1]:=x; S.Pct[i,2]:=y; { în sfârşit ne-am ales noile coordonate } MouseShow; Delay(500); b:=0 end else i:=i+1 { dacă nu e punctul i cel agăţat, poate e următorul } until b=2; { butonul 2, cel din dreapta, termină procedura } Beep; Bara; { ... nu înainte de a se introduce stilul haşurii şi a culorii } OutTextXY(10,0,'Solid=1/Empty=0 ? '); GoToXY(3,2); ReadLn(S.Fil); OutTextXY(10,27,'Culoare ? '); GoToXY(3,4);ReadLn(S.Cul) end; procedure CreatePic(var D: desen); { creează un desen D, creând pe rând, toate suprafeţele (liniile poligonale/curbele) ce îl compun } var i: Integer; begin MouseHide; Bara; {la început se precizează câte suprafeţe are desenul} OutTextXY(0,0,'Câte suprafeţe ?'); GoToXY(1,2); ReadLn(D.NrSurf); ClearDevice; MouseShow; for i:=1 to D.NrSurf do CreateSurface(D.Surf[i])

Page 96: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

96

end; procedure DrawPic(D: desen; stil: YesNo); { afişează un desen D, desenând toate suprafeţele ce îl compun, în acelaşi stil } var i: Byte; begin for i:=1 to D.NrSurf do DrawSurface(D.Surf[i], stil) end; procedure TransfPic(D1, D2: desen; n: Byte; stil: YesNo); { transformă un desen D1 într-un altul D2, în n paşi; numărul de suprafeţe ale lui D2 este acelaşi ca şi a lui D1; numărul de puncte ale fiecarei suprafeţe a lui D2, ca şi parametrii Fil şi Cul sunt aceleaşi ca şi la D1 } var S1,S2,S: surface; k,i,j: Integer; begin MouseHide; for i:=1 to n do { au loc n transformări, bazate pe translaţii } begin for k:=1 to D1.NrSurf do { se transformă, la pasul i, toate cele D1.NrSurf suprafeţe, după acelaşi model ca şi în procedura TransfSurface } begin S1:=D1.Surf[k]; S2:=D2.Surf[k]; S.NrPct:=S1.NrPct; S.Fil:=S2.Fil; S.Cul:=S2.Cul; for j:=1 to S1.NrPct do begin S.Pct[j,1]:=Round( S1.Pct[j,1]+ i*(S2.Pct[j,1]- S1.Pct[j,1])/n ); S.Pct[j,2]:=Round( S1.Pct[j,2]+ i*(S2.Pct[j,2]- S1.Pct[j,2])/n ) end; DrawSurface(S,stil); end; { aici se realizează ştergerea paginii grafice care nu se vede } SetVisualPage(1-pag); SetActivePage(pag); ClearDevice; Delay(PauzaDeAnimatie); { o mică pauză } pag:=1-pag { inversare de pagini grafice } end; MouseShow end; procedure ModifyPic(var D: desen); { modificarea ununi desen D înseamnă modificarea tuturor celor D.NrSurf linii poligonale ce îl compun }

Page 97: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

97

var i: Byte; begin for i:=1 to D.NrSurf do ModifySurface(D.Surf[i]) end; procedure SavePic(D: desen; fis: String); { salvarea datelor unui desen D într-un fişier fis; este procesul invers lui LoadPic, pe care îl prezentăm mai jos } var i,j: Byte; F: Text; begin Assign(F,fis); Rewrite(F); WriteLn(F,D.NrSurf); for i:=1 to D.NrSurf do with D.Surf[i] do begin WriteLn(F,Fil); WriteLn(F,Cul); WriteLn(F,NrPct); for j:=1 to NrPct do begin WriteLn(F,Pct[j,1]); WriteLn(F,Pct[j,2]) end end; Close(F) end; procedure LoadPic(var D: desen; fis: String); { încărcarea unui desen D dintr-un fişier fis presupune citirea din fişier a numărului de suprafeţe, apoi, pentru fiecare suprafaţă în parte, se citesc atributele Fil şi Cul, după care se citeşte câte puncte sunt; în sfârşit se citesc coordonatele tuturor punctelor acelei suprafeţe } var i,j: Byte; F: Text; begin Assign(F,fis); Reset(F); { deschiderea fişierului } ReadLn(F,D.NrSurf); for i:=1 to D.NrSurf do with D.Surf[i] do begin ReadLn(F,Fil); ReadLn(F,Cul); ReadLn(F,NrPct); for j:=1 to NrPct do begin ReadLn(F,Pct[j,1]); ReadLn(F,Pct[j,2]) end end; Close(F) end; begin PauzaDeAnimatie:=0 { valoarea implicită a pauzei } end.

Iată şi un program care pune pe utilizator să editeze un desen D1, apoi îl copie în D2, pe care utilizatorul îl modifică, salvează ambele desene, iar apoi le încarcă şi le transformă din unul în celălalt.

Page 98: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

98

program EditorDeAnimatie; uses Graph, uMouse, Crt, uAnim; var D1,D2: desen; begin OpenGraph; MouseInit; CreatePic(D1); SavePic(D1,'D1.PIC'); Sound(100); Delay(50); NoSound; MouseHide; ClearDevice; MouseShow; Delay(400); LoadPic(D2,'D1.PIC'); MouseHide; DrawPic(D2,yes); MouseShow; ModifyPic(D2); SavePic(D2,'D2.PIC'); ClearDevice; LoadPic(D1,'D1.PIC'); LoadPic(D2,'D2.PIC'); repeat TransfPic(D1,D2,20,yes); Delay(1500); ClearDevice; TransfPic(D2,D1,20,yes); Delay(1500) until keypressed; CloseGraph end.

Cel de al doilea program (Morphing) pe care vi-l prezentăm în continuare, va trebui să fie apelat din linia de comandă cu doi parametri, reprezentând numele celor două imagini între care se va face morphing, de exemplu: C : \ > morphing d1 d2 (trebuie să existe pe disc fişierele D1.PIC şi D2.PIC, create de dumneavoastră prin editare, cu primul program, de pildă).

program Morphing; { se rulează cu doi parametri, reprezentând numele celor două PIC-uri; de ex.: C:\>morphing omhom1 omhom2 sau: C:\>morphing inima1 inima2 } uses uAnim, Crt, Graph; var D1,D2: desen; begin OpenGraph; LoadPic(D1,ParamStr(1)+'.PIC'); LoadPic(D2,ParamStr(2)+'.PIC'); repeat TransfPic(D1,D2,20,yes); Delay(1500); ClearDevice; TransfPic(D2,D1,20,yes); Delay(1500) until KeyPressed; CloseGraph end.

Page 99: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

99

Dacă nu aveţi destul talent astfel încât să realizaţi nişte desene interesante, atunci vă prezentăm mai jos conţinutul a două fişiere, unul reprezentând un om, iar celălalt o casă, fişiere numite omhom1.pic, respectiv omhom2.pic. Ele sunt fişiere text, care conţin fiecare câte un număr pe fiecare linie, dar, din motive obiective, vă listăm aceste numere pe doar câteva rânduri, despărţite prin virgule, rămânând în sarcina dumneavoastră să scrieţi fişierele. - pentru fişierul omhom1.pic: 6, 1, 14, 18, 201, 81, 201, 81, 165, 104, 164, 134, 178, 169, 205, 204, 239, 225, 269, 230, 300, 231, 321, 224, 340, 205, 360, 165, 367, 137, 347, 105, 309, 75, 269, 67, 229, 70, 205, 80, 1, 1, 8, 213, 110, 213, 110, 202, 115, 209, 122, 225, 123, 239, 119, 231, 111, 215, 110, 1, 1, 8, 299, 109, 299, 109, 284, 114, 297, 122, 317, 122, 327, 114, 314, 108, 301, 108, 1, 6, 20, 163, 101, 163, 101, 121, 34, 161, 49, 168, 20, 201, 44, 227, 9, 250, 42, 277, 4, 298, 43, 341, 17, 328, 53, 370, 33, 349, 103, 311, 74, 272, 66, 229, 69, 199, 81, 165, 101, 165, 101, 1, 3, 6, 256, 137, 256, 137, 252, 178, 279, 178, 263, 136, 257, 136, 1, 4, 10, 235, 199, 235, 199, 243, 206, 259, 210, 281, 209, 292, 203, 297, 197, 269, 201, 253, 201, 237, 198 . - pentru fişierul omhom2.pic: 6, 1, 14, 18, 215, 91, 201, 92, 155, 92, 155, 132, 155, 201, 155, 241, 237, 241, 266, 241, 309, 241, 346, 241, 374, 241, 371, 165, 367, 137, 365, 91, 309, 91, 269, 91, 231, 90, 215, 91, 1, 1, 8, 217, 107, 207, 107, 195, 107, 196, 132, 243, 132, 243, 107, 228, 107, 217, 107, 1, 1, 8, 295, 107, 282, 108, 284, 114, 284, 131, 328, 131, 329, 115, 329, 107, 297, 107, 1, 6, 20, 151, 91, 63, 91, 130, 38, 151, 23, 165, 12, 201, 12, 226, 12, 259, 12, 278, 12, 319, 12, 340, 12, 358, 23, 454, 90, 365, 90, 304, 90, 268, 90,1 229, 90, 197, 91, 152, 91, 157, 89, 1, 3, 6, 253, 136, 252, 137, 254, 230, 284, 230, 284, 137, 255, 137, 1, 4, 10, 251, 232, 236, 232, 213, 232, 206, 253, 331, 253, 324, 242, 315, 232, 283, 232, 269, 232, 253, 232 . După ce aţi creat, cu mare grijă, fişierele cuprinzând numerele de mai înainte şi aţi compilat (cu tpc) programul Morphing, încercaţi o rulare cu comanda: C : \ > morphing omhom1 omhom2 şi vedeţi ce se întâmplă. Iată un moment din timpul rulării acestui program, moment în care avem pe ecran un desen intermediar ce nu reprezintă nici un om, dar nici o casă.

Un desen intermediar din timpul unui morphing

Programul se termină la apăsarea unei taste. Dacă execuţia sa v-a amuzat, atunci poate vă interesează care sunt bazele teoretice ale acestui program. În cele ce urmează vă vom prezenta o procedură care realizează o transformare a liniei poligonale S1 în linia poligonală S2, având acelaşi număr de puncte ca şi S1. Ideea care stă la baza acestei proceduri este foarte simplă.

Page 100: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

100

Cum fiecare linie poligonală are un număr de puncte unite între ele prin segmente de dreaptă, ceea ce va trebui să facem pentru ca S1 să ajungă în postura lui S2 este să facem de aşa natură ca fiecare punct a lui S1 să ajungă în punctul corespunzător din S2. Acest lucru se va realiza în n paşi, prin translaţii; la un anumit pas, punctele de pe S1 se vor afla în poziţii "intermediare" (temporare). Fie P un punct dat al lui S1; la pasul i, lui P îi corespunde un punct intermediar P '. Dacă vecinii lui P în S1 sunt A şi B, iar acestora le corespund, la pasul i, punctele intermediare A', respectiv B', atunci va trebui să îl unim pe P ' cu A' şi cu B'. Dacă P are coordonatele (x1,y1) în S1, punctul P ' va avea, la pasul i, coordonatele (x',y'), cu x’ şi y’ daţi de formulele: x' = x1 + i * (x2 - x1) şi y' = y1 + i * (y2 - y1), în care (x2,y2) reprezintă coordonatele punctului în care trebuie să ajungă punctul P în cei n paşi, adică coordonatele acestui punct în cadrul liniei poligonale S2. Iată, deci, procedura:

procedure TransfSurface(S1, S2: surface; n: Byte); var S: surface; { o linie poligonală a punctelor intermediare } i,j: Integer; begin SetVisualPage(1-pag); MouseHide; SetVisualPage(pag); MouseHide; S.NrPct:=S1.NrPct; { linia poligonală intermediara are acelaşi număr de puncte ca şi S1 şi S2 } for i:=1 to n do begin for j:=1 to S1.NrPct do begin { se calculează coordonatele punctului intermediar } S.Pct[j,1]:=Round(S1.Pct[j,1]+ i*(S2.Pct[j,1]-S1.Pct[j,1])/n); S.Pct[j,2]:=Round(S1.Pct[j,2]+ i*(S2.Pct[j,2]-S1.Pct[j,2])/n) end; SetVisualPage(1-pag); SetActivePage(pag); ClearDevice; { se şterge pagina grafică nevizibilă } DrawSurface(S,yes); { se desenează suprafaţa intermediară, aici în stilul "yes", deci complet } Delay(PauzaDeAnimatie); pag:=1-pag { se inversează paginile grafice } end; SetVisualPage(1-pag); MouseShow; SetVisualPage(pag); MouseShow end;

Procedura de mai sus stă la baza procedurii care transformă un desen în altul, prezentată în unit-ul uAnim.

Page 101: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

101

Capitolul 7. Jocuri cu prelucrări de fişiere BMP, în 256 de culori

Marea majoritate a jocurilor datorează succesul lor unor imagini deosebite cu multe culori, care se obţin din fişiere de imagine (cu una din extensiile: BMP, PCX, GIF, PIC, TIF etc.), fişiere obţinute fie cu o cameră video, fie cu un scanner. Presupunând că dispuneţi de fişiere BMP despachetate şi de programe specializate pentru a le prelucra, vă prezentăm în cele ce urmează două jocuri, care, deşi sunt foarte simple ca idee şi ca realizare, au avut un mare succes, datorat imaginilor pe care le foloseau.

7.1. Amestec

Ceea ce urmează să vă prezentăm în acest paragraf este o bagatelă. Avem o imagine pe ecran, luată dintr-un fişier BMP, cu LoadUnPackBMPFile, pe care o împărţim în pătrate. Mulţimii acestor pătrate le este asociată o matrice, fiecare componentă a matricei fiind o înregistrare cu două componente, valorile numerice -1 şi +1. +1 înseamnă că respectiva componentă este în poziţie normală, adică imaginea este în poziţia normală, care este şi cea iniţială. Dacă prima componentă X din pereche este -1, înseamnă că imaginea din pătratul corespunzător a suferit o oglindire orizontală, faţă de verticala care trece prin mijlocul pătratului în cauză. La fel, când Y este -1, înseamnă că imaginea din acel pătrat a suferit o oglindire verticală, faţă de orizontala trecând prin mijlocul pătratului. Iniţial matricea zona, asociată imaginii, are toate componentele +1, dar procedura Amestecare le schimbă, ceea ce se concretizează într-un soi de amestecare a imaginii mari, de pe ecran. De fapt au loc doar oglindiri orizontale şi verticale ale părţilor de imagine din pătratele în care este descompusă imaginea mare, nu şi interschimbarea acestor părţi (pătrate). Procedura de amestecare poate fi întreruptă de jucător la acţionarea butonului de mouse. După aceasta, jucătorul intră în procedura de Refacere, procedură care se va termina atunci când toate imaginile parţiale vor fi refăcute, adică atunci când funcţia StopJoc se va evalua la true. De fapt, această funcţie face o verificare dacă matricea zona are toate componentele (+1,+1). Pentru a oglindi orizontal imaginea dintr-o zonă, se va folosi butonul stâng al mouse-ului, care va apela procedura ReverseX, iar pentru o oglindire verticală se va acţiona butonul din dreapta, care va apela procedura ReverseY. Aceste proceduri sunt, evident, apelate şi atunci când se face amestecarea imaginii. Cele două proceduri sunt simple şi se găsesc în unit-ul MCGA, care este prezentat în anexă, la sfârşitul cărţii. Programul este prezentat în cele ce urmează, unde am îngroşat părţile principale ale sale. Observăm că se foloseşte unit-ul ViewBMP (vezi anexa !), unit din care se foloseşte procedura LoadUnPackBMPFile, procedură ce încarcă imaginea în 256 de culori, în modul grafic MCGA (320 × 200 pixeli), dintr-un fişier BMP, într-o zonă dreptunghiulară specificată prin coordonatele colţului stânga-sus. Fireşte, ne îndoim că dumneavoastră dispuneţi de fişierele AMEST01.BMP, AMEST02 şi AMEST03.BMP, care sunt necesare în acest program, însă ele sunt nişte BMP-uri de dimensiunea ecranului, BMP-uri pe care le puteţi obţine din altă parte, sau le puteţi mări din alte BMP-uri mai mici, cu o procedură de genul celei prezentate în capitolul 4. Textul programului jocului Amestec este listat mai jos:

Page 102: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

102

program Amestec; uses Crt, MCGA, ViewBMP, uMouse; const MaxNrBuc=20; latime=250; inaltime=150; autor: string = 'un joc de BOGDAN PATRUT'; type pereche=record X,Y: ShortInt end; var NrBuc,i,j, imagine: Byte; b,lat, inalt, xx,yy: Integer; zona: array[0..MaxNrBuc,0..MaxNrBuc] of pereche; sir: String; tasta:Char; Escape: Boolean; procedure Amestecare; var b,x,y,k: Integer; c: Char; begin repeat xx:=Random(NrBuc+1); yy:=Random(NrBuc+1); k:=Random(2); if k=0 then begin MouseHide; ReverseX(lat*xx,inalt*yy,lat*(xx+1)-1,inalt*(yy+1)-1); zona[xx,yy].X:=-zona[xx,yy].X; MouseShow end else begin MouseHide; ReverseY(lat*xx,inalt*yy, lat*(xx+1)-1,inalt*(yy+1)-1); zona[xx,yy].Y:=-zona[xx,yy].Y; MouseShow end; MCGAMouseData(b,x,y); Delay(30) until (b<>0) end; function StopJoc: Boolean; var i,j: Integer; SJ: Boolean; begin SJ:=True; i:=0; while (i<NrBuc) and SJ do begin j:=0; while (j<NrBuc) and SJ do if zona[i,j].X+zona[i,j].Y<>2 then SJ:=False else j:=j+1; i:=i+1 end; StopJoc:=SJ end; procedure Refacere; var b,x,y: Integer; begin

Page 103: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

103

repeat SetColor(Random(256)); PrintAt(15,24); PrintS('AMESTEC'); MCGAMouseData(b,x,y); xx := x div lat; yy := y div inalt; if xx<0 then xx:=0; if xx>NrBuc-1 then xx:=NrBuc; if yy>NrBuc-1 then yy:=NrBuc; if b=1 then begin MouseHide; ReverseX(lat*xx,inalt*yy,lat*(xx+1)-1,inalt*(yy+1)-1); zona[xx,yy].X:=-zona[xx,yy].X; MouseShow; Delay(75) end; if b=2 then begin MouseHide; ReverseY(lat*xx,inalt*yy,lat*(xx+1)-1,inalt*(yy+1)-1); zona[xx,yy].Y:=-zona[xx,yy].Y; MouseShow; Delay(75) end until (b=3) or StopJoc; if b=3 then Escape:=True end; begin Escape:=False; ClrScr; WriteLn; WriteLn(' A M E S T E C'); WriteLn(' (P), (C) 1995 Bogdan P†trut'); WriteLn; repeat Write('Dati numarul imaginii [1..3] ! > '); ReadLn(imagine) until imagine in [1..3]; OpenGraph; PrintAt(14,10); PrintS('A M E S T E C'); PrintAt(8,12); PrintS('(P),(C) 1995 Bogdan PATRUT'); NrBuc:=5; lat:=latime div NrBuc; inalt:=inaltime div NrBuc; Randomize; Str(imagine:2,sir); if sir[1]=' ' then sir[1]:='0'; LoadUnPackBMPFile(0,0,'AMEST'+sir+'.BMP'); SetColor(White); ClearView(0,inaltime+inalt+1,319, 199); ClearView(latime+lat+1,0,319,199); PrintAt(15,24); PrintS('AMESTEC'); Rectangle(0,0,latime,inaltime); for i:=1 to Length(autor) do begin PrintAt(38,i); PrintS(autor[i]) end; for i:=0 to NrBuc do for j:=0 to NrBuc do begin { iniţializare matrice de control } Rectangle(lat*i,inalt*j,lat*(i+1)-1,inalt*(j+1)-1); zona[i,j].X:=1; zona[i,j].Y:=1 end; MouseInit; Amestecare; Refacere; Delay(1000); if not Escape then

Page 104: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

104

repeat SetColor(Random(256)); Delay(500); MouseHide; PrintAt(13,12); PrintS('FELICITARI !'); MouseShow; MCGAMouseData(b,xx,yy) until (b<>0) or KeyPressed; CloseGraph; ClrScr; TextColor(White) end.

Iată, mai jos, un aspect din jocul Amestec, cu una din imaginile folosite de noi (un peisaj, cam... amestecat - ce-i drept !) şi pe care o puteţi obţine dacă scrieţi autorilor.

Aspect din jocul AMESTEC

7.2. Spânzurătoarea

Cine nu a jucat, copil fiind, cu cretă pe asfalt, împreună cu prietenii din curtea blocului Spânzurătoarea ? Unul din copii se gândeşte la un cuvânt. Îi scrie literele din capete (prima şi ultima), în locul celorlalte punând liniuţe orizontale ( _ ). Dacă, însă, printre aceste litere ar fi şi literele din capete, ele se scriu. Celălalt copil, jucătorul propriu-zis, trebuie să ghicească cuvântul la care s-a gândit primul copil. El va spune, pe rând, câte o literă, iar dacă această literă se află în cuvânt, atunci primul copil o va scrie, în locul liniuţelor, ori de câte ori apare (o dată sau de mai multe ori). Copilul al doilea va câştiga, în momentul în care va ghici întregul cuvânt. Dacă, însă, nu se nimereşte o literă, sau ea se repetă (şi deja a fost trecută, în cuvânt), atunci, primul copil va desena, la fiecare încercare nereuşită a celui de al doilea, câte un element dintr-un desen reprezentând un om spânzurat. Varianta de joc pe care v-o propunem are elementul de noutate următor: de fiecare dată, când se ghiceşte o literă, o imagine BMP este afişată, de jos în sus, tot mai mult, corespunzător procentului de litere ghicite, din totalul literelor cuvântului, exceptând, fireşte, literele de la bun început cunoscute (prima şi ultima literă din cuvânt şi orice repetare a lor). În realizarea programului am folosit nişte imagini reprezentând nişte tinere fete, imagini care pot să încânte ochii unor tineri băieţi. Ele se află în fişiere bitmap, având numele GIRL1.BMP...GIRL6.BMP. Dimensiunea acestor imagini, care folosesc o paletă cu 256 de culori,

Page 105: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

105

în rezoluţia 320 × 200 pixeli a modului grafic MCGA, este de 165 × 200 de pixeli. Există, de asemenea, o imagine specială, SPINZ.BMP, care este încărcată la începutul jocului. În textul programului, prezentat în paginile ce urmează, am pus în evidenţă procedura AfisImagine, procedură care afişează aceste bitmap-uri, parţial, în funcţie de parametrul nr. De fapt, procedura foloseşte nişte proceduri de încărcat BMP-uri, care sunt prezentate în unit-ul ViewBMP, de la sfârşitul cărţii (vezi Anexa !). Acestea sunt: PLoadUnpackBMPFile(x,y,pâna_unde,nume_fis); şi SPloadUnPackBMPFile(x,y,pâna_unde,nume_fis). Prima încarcă parţial (P) un fişier BMP (nume_fis) despachetat (256 culori), care s-ar afla în zona dreptunghiulară cu colţul stânga-sus de coordonate (x,y), dacă s-ar încărca în totalitate. Însă are loc o încărcare până la y=pâna_unde, deci imaginea de după pâna_unde nu se mai vede. Cea de a doua procedură face acelaşi lucru, însă renunţă la a mai încărca paleta de culori (deci de a seta regiştri de culoare). Deci face o încărcare simplă (S). Acest lucru durează destul de mult şi nici nu este necesar atunci când se încarcă o imagine mai mare (cu pâna_unde mai apropiat de y) peste una la fel, dar mai mică. De aceea, prima dată este apelată procedura PLoad..., iar apoi procedura SPLoad... . Desenarea spânzurătorii şi a celui spânzurat este realizat prin simple proceduri grafice, care se găsesc în unit-ul MCGA, din anexa cărţii. Numărul de cuvinte de care dispune programul este foarte limitat (10), se pot adăuga mai multe în vectorul dictionar, însă propunem cititorului să modifice programul astfel încât să se preia cuvintele dintr-un fişier text, unde eventual să fie sub o formă codificată şi să fie decodificate, înainte de a le folosi, etc. Dacă cititorul nu dispune de nişte BMP-uri care să le folosească la acest program, va trebui să aplice una din metodele descrise în capitolul 4. În ultimă instanţă va elimina acele părţi ale programului care fac apel la procedurile de încărcare de imagini şi va obţine un program funcţionabil, însă mult mai puţin atractiv. Programul se prezintă de la sine şi textul său este următorul:

program Spinzuratoarea; uses MCGA, Crt, ViewBMP; const NrMaxPasi=6; dictionar: array[1..10] of String = ('SPINZURATOARE','SCIRTIALA','INFORMATICA','CATASTROFA','GURALIV', 'POVIRNIS','PISCATURA','ALBITURA','INCONDEIAT','POPOSIT'); var cuvint,cuv,nume_fis: String; raspuns, lit1, lit2, lit: Char; i,lung,ghicite, din_start, pas: Byte; gasit, prima_data: Boolean; procedure CRectangle(x1,y1,x2,y2,c: Integer); begin SetColor(c); Rectangle(x1,y1,x2,y2) end; procedure Deseneaza; begin case pas of 1: begin CLine(10,180,100,180,12); CRectangle(80,170,120,180,12) end; 2: begin

Page 106: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

106

CLine(10,100,100,100,12); CLine(10,100,10,180,12); CLine(10,110,20,100,12); CLine(100,100,100,120,12); CLine(90,100,100,110,12) end; 3: begin CLine(100,160,90,170,11); CLine(100,160,110,170,11) end; 4: begin CLine(100,140,100,160,11);CLine(100,143,90,153,11); CLine(100,143,110,153,11) end; 5: begin SetColor(14); Circle(100,130,10); SetColor(12); Circle(95,127,2); Circle(105,127,2); CLine(100,130,100,135,15); CLine(97,137,103,137,10) end; 6: begin CRectangle(80,170,120,180,0); SetColor(15) end end end; procedure AfisImagine(k,n: Byte); begin if prima_data then begin ClearView(160,0,319,199); PLoadUnpackBMPFile(150,0,Round(200*k/n),nume_fis); prima_data:=False end else SPloadUnPackBMPFile(150,0,Round(200*k/n),nume_fis) end; procedure AfiseazaCuvintul; var i: Byte; begin PrintAt(1,8); PrintS(cuv) end; procedure Prezentare; begin LoadUnPackBMPFile(160,0,'Spinz.BMP') end; begin repeat OpenGraph; PrintAt(21,10); PrintS('Asteptati !'); Prezentare; SetColor(40); PrintAt(1,2); PrintS(' SPINZURATOAREA'); SetColor(20); PrintAt(1,3); PrintS(' ---------------'); SetColor(15); Randomize; i:=Random(6)+1; Str(i:1,nume_fis); nume_fis:='GIRL'+nume_fis+'.BMP'; prima_data:=True; cuvint:=dictionar[Random(10)+1]; lung:=Length(cuvint); lit1:=cuvint[1]; lit2:=cuvint[lung]; PrintAt(1,8); PrintS(lit1); cuv[0]:=chr(lung); cuv[1]:=lit1; cuv[lung]:=lit2; ghicite:=2; for i:=2 to lung-1 do if cuvint[i] in [lit1, lit2] then begin PrintS(cuvint[i]); cuv[i]:=cuvint[i]; Inc(ghicite) end

Page 107: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

107

else begin PrintS('.'); cuv[i]:='.' end; PrintS(lit2); din_start:=ghicite; pas:=0; repeat PrintAt(1,10); SetColor(15); PrintS('Dati litera = '); lit:=UpCase(ReadKey); PrintS(lit); Delay(500); PrintAt(15,10); PrintS(' '); gasit:=False; for i:=1 to lung do if cuv[i]='.' then if cuvint[i]=lit then begin cuv[i]:=lit; gasit:=True; Inc(ghicite); AfisImagine(ghicite-din_start, lung-din_start) end; if not gasit then begin Inc(pas); Deseneaza end else AfiseazaCuvintul until (cuv=cuvint) or (pas=NrMaxPasi); PrintAt(1,10); PrintS('JOC TERMINAT...'); lit:=ReadKey; CloseGraph; TextMode(CO40); if cuv=cuvint then WriteLn('FELICITARI !') else WriteLn('Mai învăţaţi limba română !...'); Delay(3000); WriteLn; Write('Continuati ? [D/N] > '); raspuns:=ReadKey; raspuns:=ReadKey until raspuns in ['n','N']; TextMode(CO80); ClrScr end.

Imagine din timpul unui joc SPÂNZURĂTOAREA

Page 108: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

108

Anexa 1. Fişierele de ajutor ale programului High-3D Programul High-3D are posibilitatea de a obţine informaţii ajutătoare, de tip "help", dacă se acţionează butonul din dreapta, în dreptul unei comenzi sau chiar în interiorul zonei de afişare. Cele două fişiere care conţin textele ajutătoare sunt următoarele, corespunzătoare celor două meniuri: Fişierul MY3D1.HLP: ### Comanda LINIE Aceasta comanda va permite sa trasati segmente de dreapta. Trasarea unui segment se face selectând prin apasarea butonului stâng al mouse-ului primul punct, iar apoi, tinând butonul apasat, deplasati mouse-ul pâna va hotarâti unde sa fie cel de al doilea punct. In acest moment dati drumul la buton ### Comanda DREPTUNGHI Aceasta comanda va permite sa trasati dreptunghiuri, selectând cu butonul stâng al mouse-ului colturile stânga-sus si dreapta-jos ale dreptunghiului ce va fi trasat. Dupa ce ati stabilit unul din colturi, tineti butonul mouse-ului pâna va hotarâti la celalalt colt. Fireste, dreptunghiurile vor avea laturile verticale si orizontale. ### Comanda Copie Daca sunteti in prima sectiune, aceasta se va curata, daca nu, atunci sectiunea curenta se va sterge si se va copia in ea tot ce era in sectiunea precedenta (, ca numar). ### Comanda Mutare Selectând cu mouse-ul o latura (agatându-i mijlocul), aceasta se poate muta intr-o alta pozitie. Noua pozitie se stabileste când se da drumul la butonul din stânga al mouse-ului. ### Comanda Coef - Micsoreaza valoarea coeficientului valabil pentru urmatoarea comanda Redim. . Micsorarea se face cu 0.1. Coeficientul este afisat in partea de jos a ecranului. Marirea sa se poate face cu Coef +. ### Comanda Coef + Mareste valoarea coeficientului valabil pentru urmatoarea comanda Redim. . Marirea se face cu 0.5. Coeficientul este afisat in partea de jos a ecranului. Micsorarea sa se poate face cu Coef -. ### Comanda Redim. Aceasta comanda permite redimensionarea figurii din sectiunea curenta cu coeficientul afisat in partea de jos a ecranului. Acesta poate fi modificat cu Coef - si Coef +. Comanda este necesara pentru crearea de corpuri sun forma de trunchi, dar trebuie ca bazele sa fie centrate in mijlocul ecranului. ### Comanda Z -

Micsoreaza valoarea cotei viitoare cu 1. ### Comanda Z + Mareste valoarea cotei viitoare cu 10 unitati. ### Comanda Z viitor Stabileste valoarea cotei curente ca fiind cota viitoare. ### Comanda Vizualizare Trece la in meniul de vizualizare si rotatie a corpului ### EDITARE CORP Acest moment al programului editeaza corpul tridimensional folosind urmatoarea tehnica: -se creeaza sectiuni paralele cu xOy; -aceste sectiuni difera prin z si ele se leaga prin punctele corespunzatoare; -intr-o sectiune se deseneaza mai multe linii; -alegerea lui z viitor se face cu comenzile Z. Pentru detalii cititi documentatia. Pt. a vedea corpul editat, apasati Viz.3D. ###

Fişierul MY3D2.HLP: ### Comanda Unghi - Este vorba de micsorarea cu 5 unitati a unghiului de rotatie a corpului fata de cele trei axe Ox, Oy si Oz. Acest unghi este afisat in partea de jos a ecranului. ### Comanda Unghi + Este vorba de marirea cu 10 unitati a unghiului de rotatie a corpului fata de cele trei axe Ox, Oy si Oz. Acest unghi este afisat in partea de jos a ecranului. ### Comanda OX - Aceasta comanda determina executarea unei rotatii cu un -unghi, fata de axa OX. Unghiul de rotatie este afisat in partea de jos a ecranului si poate fi modificat cu comenzile Unghi - si Unghi +. ### Comanda OX + Aceasta comanda determina executarea unei rotatii cu un unghi, fata de axa OX. Unghiul de rotatie este afisat in partea de jos a ecranului si poate fi modificat cu comenzile Unghi - si Unghi +. ### Comanda OY - Aceasta comanda determina executarea unei

Page 109: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

109

rotatii cu un -unghi, fata de axa OY. Unghiul de rotatie este afisat in partea de jos a ecranului si poate fi modificat cu comenzile Unghi - si Unghi +. ### Comanda OY + Aceasta comanda determina executarea unei rotatii cu un unghi, fata de axa OY. Unghiul de rotatie este afisat in partea de jos a ecranului si poate fi modificat cu comenzile Unghi - si Unghi +. ### Comanda OZ - Aceasta comanda determina executarea unei rotatii cu un -unghi, fata de axa OZ. Unghiul de rotatie este afisat in partea de jos a ecranului si poate fi modificat cu comenzile Unghi - si Unghi +. ### Comanda OZ + Aceasta comanda determina executarea unei rotatii cu un unghi, fata de axa OZ. Unghiul de rotatie este afisat in partea de jos a ecranului si poate fi modificat cu comenzile Unghi - si Unghi +. ### Comanda Desen Se face revenirea la primul meniu, se pierde corpul prezent si se va trece la editarea altui corp. ### Comanda Salveaza Corpul creat in acest meniu poate fi salvat intr-un fisier, urmind ca el sa poata fi incarcat alta data, pentru a fi vizualizat. ### Comanda Incarca Incarca un corp creat anterior si salvat

cu comanda Salveaza, dintr-un fisier. Corpul poate fi vizualizat si rotit, nu insa si modificat. ### PROGRAM DE GRAFICA TRIDIMENSIONALA +++++++++++++++++++++++++++++++++++++++++ + # # # #### # # #### #### + + # # # # # # # # + + ##### # # ## ##### ## ## # # + + # # # # # # # # # # + + # # # #### # # #### #### + +++++++++++++++++++++++++++++++++++++++++ Realizat de Bogdan Patrut, 1994, 1995. Toate drepturile asupra acestui program apartin autorului. Modificarea, copierea sau distribuirea acestui program fara permisiunea scrisa a autorului sint strict interzise si vor fi pedepsite conform legilor României. ### PROGRAM DE GRAFICA TRIDIMENSIONALA +++++++++++++++++++++++++++++++++++++++++ + # # # #### # # #### #### + + # # # # # # # # + + ##### # # ## ##### ## ## # # + + # # # # # # # # # # + + # # # #### # # #### #### + +++++++++++++++++++++++++++++++++++++++++ Pentru a obtine un ajutor ("help") la o anumita comanda, din oricare meniu, plasati mouse-ul in dreptul acelei comenzi si actionati butonul din dreapta. Se poate obtine ajutor si la editor, daca actionati butonul din dreapta al mouse-ului in interiorul ferestrei de editare. ###

Page 110: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

110

Anexa 2. Uniturile uMouse, MCGA şi ViewBMP

În continuare sunt prezentate listing-urile celor trei unit-uri de interes general, folosite de programele de grafică (VGA şi MCGA) din această carte: 1. Unit-ul uMouse cuprinde proceduri de lucru cu mouse-ul în modurile grafice. Pentru modul grafic MCGA avem o procedură de citire a stării mouse-ului, numită MCGAMouseData, în unit-ul corespunzător.

unit umouse; [ proceduri de lucru cu mouse-ul ] interface var MouseInst: Boolean; procedure MouseInit; [ verifică dacă există mouse; dacă da, îl iniţializează şi pune MouseInst pe True ] procedure MouseShow; [ face să apară săgeata pe ecran ] procedure MouseHide; [ face să dispară săgeata de pe ecran ] procedure MouseData(var buton, x,y: Integer); [ obţine poziţia mouse-ului şi valoarea butonului apăsat: 0 = nici un buton; 1 = buton stânga; 2 = buton dreapta; 3 = ambele ] procedure MouseMove(x,y: Integer); [ mută mouse-ul în poziţia specificată ] implementation procedure MouseInit; assembler; asm MOV AX,0; INT 33h; MOV MouseInst,AL; MOV AX,1; INT 33h end; procedure MouseShow; assembler; asm MOV AX,1; INT 33h end; procedure MouseHide; assembler; asm MOV AX,2; INT 33h end; procedure MouseData; var a,b: Integer; c: Byte; begin asm MOV AX,3; INT 33h; MOV a,CX; MOV b,DX; MOV c,BL end; x:=a; y:=b; buton:=c end; procedure MouseMove; assembler; asm MOV AX,4; MOV CX,x; MOV DX,y; INT 33h end; end.

2. Unit-ul MCGA cuprinde proceduri generale de grafică, care pot fi folosite în grafica cu 256 culori, în modul grafic MCGA (320 × 200 pixeli); fireşte, pot fi folosite unele proceduri şi în alte moduri grafice. Modul grafic se poate alege cu SetVideoMode. Spre deosebire de varianta prezentată în lucrarea “Grafică în OOP ...şi nu numai...”, textul de aici al acestui unit cuprinde şi o procedură care traseaza o elipsă de centru şi semiaxe date, Oval.

unit MCGA; interface var WriteMode: Byte; procedure MCGAMouseData(var b,x,y:integer); procedure SetVideoMode(VideoCode: Byte); procedure SetColorRegister(RegColor: Word;

Page 111: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

111

RedValue, GreenValue, BlueValue: Byte); procedure ReadColorRegister(RegColor: Word; var RedValue, GreenValue, BlueValue: Byte); procedure OpenGraph; procedure CloseGraph; function GetMaxX: Integer; function GetMaxY: Integer; procedure SetColor(c: Byte); function GetColor: Byte; procedure SetBkColor(c: Byte); function GetBkColor: Byte; procedure PutPixel(X, Y: Integer; Pixel: Byte); function GetPixel(X, Y: Integer): Byte; procedure SetWriteMode(modul: Byte); procedure Line(x0,y0,x1,y1: Integer); procedure CLine(x0,y0,x1,y1: Integer; c: Byte); procedure Rectangle(x11,y11,x22,y22:integer); procedure Circle(x,y, r: Integer); procedure Oval(xc,yc,a,b: Integer); procedure Box(x1,y1,x2,y2: Integer; c: Byte); procedure ClearView(x1,y1,x2,y2: Integer); procedure ReverseY(x1,y1,x2,y2: Integer); procedure ReverseX(x1,y1,x2,y2: Integer); procedure PrintAt(x,y: Byte); procedure Advance; procedure PrintC(a: Char); procedure PrintS(s: String); procedure Print(n: Integer); implementation uses uMouse, Crt; function Min(x,y:integer):integer; begin if x<y then Min:=x else Min:=y end; function Max(x,y:integer):integer; begin if x>y then Max:=x else Max:=y end; procedure MCGAMouseData(var b,x,y:integer); [ citire coordonate şi buton mouse, în modul grafic 320 x 200 ] var x1:integer; begin MouseData(b,x1,y); x:=x1 div 2 end; var _color_:byte; [ culoarea curenă ] _bk_color_:byte; [ culoarea fondului ] _x_poz_, _y_poz_, [ poziţia curentă a cursorului ], _x_max_, _y_max_: Integer; [colţul dreapta-jos ] procedure SetVideoMode; assembler; [ setează modul de scriere ] asm MOV AL, VideoCode; XOR AH,AH; INT 10h end; procedure SetColorRegister; assembler; [ setează un registru de culoare 0 =< RegValue <= 255 ] asm MOV AX,1010H; MOV BX,RegColor; MOV CH,GreenValue; MOV CL,BlueValue; MOV DH,RedValue; INT 10h end;

Page 112: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

112

procedure ReadColorRegister; assembler; [ citeşte un registru de culoare ] asm MOV AX,1015H; MOV BX,RegColor; INT 10H; LES BX,RedValue; MOV BYTE PTR ES:[BX],DH; LES BX,GreenValue; MOV BYTE PTR ES:[BX],CH; LES BX,BlueValue; MOV BYTE PTR ES:[BX],CL end; procedure PutPixel(X, Y: Integer; Pixel: Byte); begin if WriteMode = 1 then [ pentru modul de scriere Xor ] Pixel := Pixel XOR GetPixel(X,Y); asm MOV AL,Pixel; MOV AH,0CH; MOV CX,X; MOV DX,Y; INT 10h end end; function GetPixel(X, Y: Integer): Byte; assembler; asm MOV AH,0DH; MOV CX,X; MOV DX,Y; INT 10H end; procedure OpenGraph; [ iniţializare grafică ] begin SetVideoMode($13); _x_max_:=319; _y_max_:=219; SetColor(15); WriteMode:=0 end; function GetMaxX: Integer; begin GetMaxX := _x_max_ end; function GetMaxY: Integer; begin GetMaxY := _y_max_ end; procedure SetColor; begin _color_:=c end; function GetColor:byte; begin GetColor:=_color_ end; procedure SetBkColor(c:byte); begin _bk_color_:=c end; function GetBkColor:byte; begin GetBkColor:=_bk_color_ end; procedure PrintAt; assembler; [ poziţionare cursor pentru afişare cu PrintS sau Print ] asm PUSH BP; MOV AH,$02; MOV DH,y; MOV DL,x; MOV BH,0; INT $10; POP BP end; procedure Advance; assembler; [ avansare poziţie cursor pentru scriere ] asm PUSH BP; MOV AH,$03; MOV BH,$00; INT $10; INC DL; MOV AH,$02; MOV BH,$00; INT $10; POP BP end; procedure PrintC(a:char); [ afişează în poziţia curentă a cursorului de scriere un caracter a ] var c:integer;

Page 113: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

113

begin c:=GetColor; asm PUSH BP; MOV AH,$09; MOV AL,a; MOV BX,c; MOV CX,1; INT $10; POP BP end; Advance end; procedure PrintS; [ afişează un şir de caractere ] var i:byte; begin for i:=1 to Length(s) do PrintC(s[i]) end; procedure Print; [ afişează un număr întreg] var s:String[10]; begin Str(n,s); PrintS(s) end; procedure Box; [ umple o zonă dreptunghiulară într-o culoare ] var i,j:integer; begin for i:=x1 to x2 do for j:=y1 to y2 do PutPixel(i,j,c) end; procedure Line(x0,y0,x1,y1: Integer); [ trasează o linie între (x0,y0) şi (x1,y1) ] var a,b,c,a2,b2,ab2,ds,xinc,yinc,u,v,i: Integer; begin c:=GetColor; u:=x0; v:=y0; a:=abs(y1-y0); b:=abs(x1-x0); a2:=a*2; b2:=b*2; if x0<x1 then xinc:=1 else xinc:=-1; if y0<y1 then yinc:=1 else yinc:=-1; PutPixel(u,v,c); if (b>a) then begin ds:=a2-b; ab2:=a2-b2; for i:=1 to b do begin if (ds>=0) then begin Inc(v,yinc);Inc(ds,ab2) end else Inc(ds,a2); Inc(u,xinc); PutPixel(u,v,c) end end else begin ds:=b2-a; ab2:=b2-a2; for i:=1 to a do begin if (ds>=0) then begin Inc(u,xinc); Inc(ds,ab2) end else Inc(ds,b2); Inc(v,yinc); PutPixel(u,v,c) end end end; procedure CLine(x0,y0,x1,y1:integer; c:byte); [ trasează o linie de culoare c; nu afectează culoarea curentă ] var cc:byte; begin cc:=GetColor; SetColor(c); Line(x0,y0,x1,y1); SetColor(cc) end; procedure Rectangle(x11,y11,x22,y22:integer); [ trasează un dreptunghi de la (x11,y11) la (x22,y22) ] var x1,y1,x2,y2:integer; begin x1 := min(x11,x22); y1 := min(y11,y22);

Page 114: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

114

x2 := max(x11,x22); y2 := max(y11,y22); CLine(x1,y1,x2,y1,_color_); CLine(x2,y1,x2,y2,_color_); CLine(x1,y1,x1,y2,_color_); CLine(x1,y2,x2,y2,_color_) end; procedure ClearView(x1,y1,x2,y2:integer); [ şterge o zonă dreptunghiulară ] begin Box(x1,y1,x2,y2,GetBkColor) end; procedure SetWriteMode(modul: Byte); [ alege modul de scriere ] begin WriteMode := modul end; procedure CloseGraph; [ părăseşte modul grafic ] begin SetVideoMode(3) end; procedure Circle(x,y,r: Integer); [ trasează un cerc de centru (x,y) şi rază r, în culoarea curentă ] var a,b,c,d: LongInt; begin a:=r;b:=0;c:=r;d:=0; repeat PutPixel(x+a,y+b,_color_); PutPixel(x-a,y+b,_color_); PutPixel(x-a,y-b,_color_); PutPixel(x+a,y-b,_color_); PutPixel(x+b,y+a,_color_); PutPixel(x-b,y+a,_color_); PutPixel(x-b,y-a,_color_); PutPixel(x+b,y-a,_color_); Inc(d,b+b+1); Inc(b); if d>c then begin inc(c,a+a+1);dec(a) end; until a<b end; procedure Oval(xc,yc,a,b: Integer); [ trasează o elipsă, cu centrul în (xc,yc), de semiaxe a şi b ] [ trasarea este rapidă; se folosesc doar adunări, scăderi, înmulţiri ] [ această procedură nu figura în unit-ul MCGA prezentat la finele ] [ lucrării "Grafică în OOP ...şi nu numai..." ] var x,y: Integer; aa,aa2,bb,bb2,d,dx,dy: LongInt; begin x := 0; y := b; aa := LongInt(a) * a; aa2 := 2 * aa; bb := LongInt(b) * b; bb2 := 2 * bb; d := bb - aa * b + aa div 4; dx := 0; dy := aa2 * b; PutPixel(xc,yc-y,_color_); PutPixel(xc,yc+y,_color_); PutPixel(xc-a,yc,_color_); PutPixel(xc+a,yc,_color_); while dx < dy do begin if d>0 then begin Dec(y); Dec(dy,aa2); Dec(d,dy) end; Inc(x); Inc(dx,bb2); Inc(d,bb+dx); PutPixel(xc+x,yc+y,_color_); PutPixel(xc-x,yc+y,_color_); PutPixel(xc+x,yc-y,_color_); PutPixel(xc-x,yc-y,_color_) end; Inc(d, (3 * (aa-bb) div 2 - (dx+dy)) div 2); while y>0 do begin if d<0 then begin Inc(x); Inc(dx,bb2); Inc(d,bb+dx) end; Dec(y); Dec(dy,aa2); Inc(d,aa-dy); PutPixel(xc+x,yc+y,_color_); PutPixel(xc-x,yc+y,_color_); PutPixel(xc+x,yc-y,_color_); PutPixel(xc-x,yc-y,_color_) end end; procedure ReverseX(x1,y1,x2,y2: Integer); [ oglindeşte faţă de verticala din centru, o zona dreptunghiulară ]

Page 115: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

115

var i,j, aux: Integer; begin for j:=y1 to y2 do for i:=x1 to (x1+x2) div 2 do begin aux := GetPixel(i,j); PutPixel(i,j,GetPixel(x1+x2-i,j)); PutPixel(x1+x2-i,j,aux) end end; procedure ReverseY(x1,y1,x2,y2: Integer); [ oglindeşte faţă de orizontala din centru, o zona dreptunghiulară ] var i,j, aux: Integer; begin for i:=x1 to x2 do for j:=y1 to (y1+y2) div 2 do begin aux := GetPixel(i,j); PutPixel(i,j,GetPixel(i,y1+y2-j)); PutPixel(i,y1+y2-j,aux) end end; end.

3. Unit-ul ViewBMP cuprinde câteva proceduri de lucru cu fişiere bitmap (.BMP): încărcări (complete (adică şi paleta de culori şi informaţia), simple (doar informaţia), parţiale) şi salvări pe disc. Sunt prezentate proceduri atât pentru bitmap-uri împachetate (16 culori), cât şi pentru cele despachetate (256 culori).

unit ViewBMP; interface uses Dos; procedure LoadPackBMPFile(x, y: LongInt; NameCode: PathStr); procedure SavePackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr); procedure LoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr); procedure SaveUnPackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr); procedure SLoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr); procedure PLoadUnPackBMPFile(x,y: LongInt; nr: Integer; NameCode: PathStr); procedure SPLoadUnPackBMPFile(x,y: LongInt; nr: Integer; NameCode: PathStr); function BMPError: Boolean; implementation uses BMPTypes, MCGA; var testH: BitMapFileHeader; testI: BitMapInfo; cRed, cGreen, cBlue: byte; cRGB: RGBQuad; cReg: byte;

Page 116: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

116

fBMP: file; iBMP, jBMP: LongInt; memBMP: byte; readByte: byte; LineBuff: array[0..639] of byte; Error: Boolean; procedure LoadPackBMPFile(x, y: LongInt; NameCode: PathStr); begin Assign(fBMP, NameCode); Reset(fBMP, 1); BlockRead(fBMP, testH, SizeOf(testH)); BlockRead(fBMP, testI.H, SizeOf(testI.H)); if testI.H.BitCount < 4 then begin SetVideoMode(3); Error := True; Exit; end else if testI.H.ClrImportant >= 0 then begin for iBMP := 0 to 15 do begin BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4; testI.C[iBMP].Green := testI.C[iBMP].Green div 4; testI.C[iBMP].Red := testI.C[iBMP].Red div 4; end; end; for iBMP := 0 to 5 do SetColorRegister(iBMP, testI.C[iBMP].Red, testI.C[iBMP].Green, testI.C[iBMP].Blue); SetColorRegister(20, testI.C[6].Red, testI.C[6].Green, testI.C[6].Blue); SetColorRegister(7, testI.C[7].Red, testI.C[7].Green, testI.C[7].Blue); for iBMP := 56 to 63 do SetColorRegister(iBMP, testI.C[iBMP - 48].Red, testI.C[iBMP - 48].Green, testI.C[iBMP - 48].Blue); for iBMP := 0 to testI.H.Height - 1 do begin jBMP := 0; repeat BlockRead(fBMP, readByte, 1); memBMP := readByte; readByte := memBMP shr 4; PutPixel(x + jBMP, y + testI.H.Height - iBMP - 1, readByte); Inc(jBMP); memBMP := memBMP and 15; PutPixel(x + jBMP, y + testI.H.Height - iBMP - 1, memBMP); Inc(jBMP); until jBMP = testI.H.Width; end; Close(fBMP); end; procedure SavePackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr); begin Assign(fBMP, NameCode); Rewrite(fBMP, 1); testH.Types[1] := 'B';

Page 117: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

117

testH.Types[2] := 'M'; testH.Size := Round((x2 - x1 + 1) * (y2 - y1 + 1) / 2) + 118; testH.Reserved1 := 0; testH.Reserved2 := 0; testH.OffBits := 118; testI.H.Size := 40; testI.H.Width := x2 - x1 + 1; testI.H.Height := y2 - y1 + 1; testI.H.Planes := 1; testI.H.BitCount := 4; testI.H.Compression := 0; testI.H.SizeImage := 0; testI.H.XPelsPerMeter := 0; testI.H.YPelsPerMeter := 0; testI.H.ClrUsed := 0; testI.H.ClrImportant := 0; BlockWrite(fBMP, testH, SizeOf(testH)); BlockWrite(fBMP, testI.H, SizeOf(testI.H)); for iBMP := 0 to 5 do begin ReadColorRegister(iBMP, cRed, cGreen, cBlue); testI.C[iBMP].Blue := cBlue * 4; testI.C[iBMP].Green := cGreen * 4; testI.C[iBMP].Red := cRed * 4; testI.C[iBMP].Reserved := 0; BlockWrite(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); end; ReadColorRegister(20, cRed, cGreen, cBlue); testI.C[6].Blue := cBlue * 4; testI.C[6].Green := cGreen * 4; testI.C[6].Red := cRed * 4; testI.C[6].Reserved := 0; BlockWrite(fBMP, testI.C[6], SizeOf(testI.C[6])); ReadColorRegister(7, cRed, cGreen, cBlue); testI.C[7].Blue := cBlue * 4; testI.C[7].Green := cGreen * 4; testI.C[7].Red := cRed * 4; testI.C[7].Reserved := 0; BlockWrite(fBMP, testI.C[7], SizeOf(testI.C[7])); for iBMP := 56 to 63 do begin ReadColorRegister(iBMP, cRed, cGreen, cBlue); testI.C[iBMP - 48].Blue := cBlue * 4; testI.C[iBMP - 48].Green := cGreen * 4; testI.C[iBMP - 48].Red := cRed * 4; testI.C[iBMP - 48].Reserved := 0; BlockWrite(fBMP, testI.C[iBMP - 48], SizeOf(testI.C[iBMP - 48])); end; jBMP := y2; repeat iBMP := x1; repeat readByte := GetPixel(iBMP, jBMP) shl 4; Inc(iBMP); readByte := readByte + GetPixel(iBMP, jBMP); BlockWrite(fBMP, readByte, 1); Inc(iBMP); until iBMP > x2; jBMP := jBMP - 1;

Page 118: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

118

until jBMP < y1; Close(fBMP); end; procedure LoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr); begin Assign(fBMP, NameCode); Reset(fBMP, 1); BlockRead(fBMP, testH, SizeOf(testH)); BlockRead(fBMP, testI.H, SizeOf(testI.H)); if testI.H.BitCount < 4 then begin SetVideoMode(3); Error := True; Exit end else if (testI.H.BitCount = 8) and (testI.H.ClrImportant >= 0) then begin for iBMP := 0 to 255 do begin BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4; testI.C[iBMP].Green := testI.C[iBMP].Green div 4; testI.C[iBMP].Red := testI.C[iBMP].Red div 4; end; end; for iBMP := 0 to 255 do SetColorRegister(iBMP, testI.C[iBMP].Red, testI.C[iBMP].Green, testI.C[iBMP].Blue); for iBMP := 0 to testI.H.Height - 1 do begin BlockRead(fBMP, LineBuff, testI.H.Width); for jBMP := 0 to testI.H.Width - 1 do Mem[$A000: jBMP + x + 320 * y + 320 * (testI.H.Height - iBMP - 1)] := LineBuff[jBMP]; end; Close(fBMP); end; procedure PLoadUnPackBMPFile(x, y: LongInt; nr: Integer; NameCode: PathStr); begin Assign(fBMP, NameCode); Reset(fBMP, 1); BlockRead(fBMP, testH, SizeOf(testH)); BlockRead(fBMP, testI.H, SizeOf(testI.H)); if testI.H.BitCount < 4 then begin SetVideoMode(3); Error := True; Exit end else if (testI.H.BitCount = 8) and (testI.H.ClrImportant >= 0) then begin for iBMP := 0 to 255 do begin BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4; testI.C[iBMP].Green := testI.C[iBMP].Green div 4;

Page 119: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

119

testI.C[iBMP].Red := testI.C[iBMP].Red div 4; end; end; for iBMP := 0 to 255 do SetColorRegister(iBMP, testI.C[iBMP].Red, testI.C[iBMP].Green, testI.C[iBMP].Blue); for iBMP := 0 to nr - 1 do begin BlockRead(fBMP, LineBuff, testI.H.Width); for jBMP := 0 to testI.H.Width - 1 do Mem[$A000: jBMP + x + 320 * y + 320 * (testI.H.Height - iBMP - 1)] := LineBuff[jBMP]; end; Close(fBMP); end; procedure SLoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr); begin Assign(fBMP, NameCode); Reset(fBMP, 1); BlockRead(fBMP, testH, SizeOf(testH)); BlockRead(fBMP, testI.H, SizeOf(testI.H)); if testI.H.BitCount < 4 then begin SetVideoMode(3); Error := True; Exit end else if (testI.H.BitCount = 8) and (testI.H.ClrImportant >= 0) then begin for iBMP := 0 to 255 do begin BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4; testI.C[iBMP].Green := testI.C[iBMP].Green div 4; testI.C[iBMP].Red := testI.C[iBMP].Red div 4; end; end; for iBMP := 0 to testI.H.Height - 1 do begin BlockRead(fBMP, LineBuff, testI.H.Width); for jBMP := 0 to testI.H.Width - 1 do Mem[$A000: jBMP + x + 320 * y + 320 * (testI.H.Height - iBMP - 1)] := LineBuff[jBMP]; end; Close(fBMP) end; procedure SPLoadUnPackBMPFile(x, y: LongInt; nr: Integer; NameCode: PathStr); begin Assign(fBMP, NameCode); Reset(fBMP, 1); BlockRead(fBMP, testH, SizeOf(testH)); BlockRead(fBMP, testI.H, SizeOf(testI.H)); if testI.H.BitCount < 4 then

Page 120: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

120

begin SetVideoMode(3); Error := True; Exit end else if (testI.H.BitCount = 8) and (testI.H.ClrImportant >= 0) then begin for iBMP := 0 to 255 do begin BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4; testI.C[iBMP].Green := testI.C[iBMP].Green div 4; testI.C[iBMP].Red := testI.C[iBMP].Red div 4; end; end; for iBMP := 0 to nr - 1 do begin BlockRead(fBMP, LineBuff, testI.H.Width); for jBMP := 0 to testI.H.Width - 1 do Mem[$A000: jBMP + x + 320 * y + 320 * (testI.H.Height - iBMP - 1)] := LineBuff[jBMP]; end; Close(fBMP) end; procedure SaveUnPackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr); begin Assign(fBMP, NameCode); Rewrite(fBMP, 1); testH.Types[1] := 'B'; testH.Types[2] := 'M'; testH.Size := (x2 - x1 + 1) * (y2 - y1 + 1) + 1078; testH.Reserved1 := 0; testH.Reserved2 := 0; testH.OffBits := 1078; testI.H.Size := 40; testI.H.Width := x2 - x1 + 1; testI.H.Height := y2 - y1 + 1; testI.H.Planes := 1; testI.H.BitCount := 8; testI.H.Compression := 0; testI.H.SizeImage := 0; testI.H.XPelsPerMeter := 0; testI.H.YPelsPerMeter := 0; testI.H.ClrUsed := 256; testI.H.ClrImportant := 256; BlockWrite(fBMP, testH, SizeOf(testH)); BlockWrite(fBMP, testI.H, SizeOf(testI.H)); for iBMP := 0 to 255 do begin ReadColorRegister(iBMP, cRed, cGreen, cBlue); testI.C[iBMP].Blue := cBlue * 4; testI.C[iBMP].Green := cGreen * 4; testI.C[iBMP].Red := cRed * 4; testI.C[iBMP].Reserved := 0; BlockWrite(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP])); end; for iBMP := 0 to testI.H.Height - 1 do begin

Page 121: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

121

for jBMP := 0 to testI.H.Width - 1 do LineBuff[jBMP] := Mem[$A000: jBMP + x1 + 320 * y1 + 320 * (testI.H.Height - iBMP - 1)]; BlockWrite(fBMP, LineBuff, testI.H.Width); end; Close(fBMP); end; function BMPError; begin BMPError := Error; Error := False end; end.

Unit-ul de mai sus foloseşte formatul BMP care se găseşte în unit-ul BMPTypes, prezentat mai jos:

unit BMPTypes; interface type BitMapFileHeader = record Types: array[1..2] of char; Size: Longint; Reserved1: Word; Reserved2: Word; OffBits: Longint; end; BitMapInfoHeader = record Size: Longint; Width: Longint; Height: Longint; Planes: Word; BitCount: Word; Compression: Longint; SizeImage: Longint; XPelsPerMeter: Longint; YPelsPerMeter: Longint; ClrUsed: Longint; ClrImportant: Longint; end; RGBQuad = record Blue: Byte; Green: Byte; Red: Byte; Reserved: Byte; end; BitMapInfo = record H: BitMapInfoHeader; C: array[0..255] of RGBQuad; end; implementation

end.

Page 122: Grafică 3D, animaţie şi jocuri · reuşit să meargă prea departe, îl sfătuim să nu dezarmeze, ci să citească propunerile de jocuri din această lucrare şi, ajutaţi de

122

Bibliografie

1. Aspru, O. - Grafică în Turbo C, Editura ADIAS, Rm. Vâlcea, 1994.

2. Kassera, W, Kassera, V. - Programarea în Turbo Pascal 6.0, Editura TIPOMUR, Tg. Mureş, 1992.

3. Kent, J., Brumbaugh, H. - Turbo C FLI Library Documentation for FLI Files Created by Autodesk Animator, Dancing Flame, San Francisco, SUA, 1989.

4. Pătruţ, B. - Grafică în OOP ... şi nu numai..., Editura ADIAS, Rm. Vâlcea, 1995.

5. Pătruţ, B. - Învăţaţi limbajul Pascal în 12 lecţii, Editura TEORA, Bucureşti, 1997-2004.

6. Tănăsescu, A., Constantin, R., Marinescu, I. D., Busuioc, L. - Grafică asistată. Programe FORTRAN pentru reprezentări geometrice, Editura Tehnică, Bucureşti, 1989.