Documente Academic
Documente Profesional
Documente Cultură
EduSoft 2005
Redactor: Tiberiu SOCACIU
004.42C
Editura EduSoft
600065 Bacău, str. 9 Mai, nr. 82, sc. C, ap. 13
E-mail: contact@edusoft.ro, Web: www.edusoft.ro
ISBN 973-87496-2-X
2
Bogdan PĂTRUŢ Iulian Marius FURDU
bogdan@edusoft.ro iulianfurdu@personal.ro
Editura EduSoft
Bacău 2005
3
CUPRINS
Introducere 5
Bibliografie 122
4
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
5
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
6
Capitolul 1. Puţină geometrie proiectivă.
Programul High - 3D
• 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
Î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
7
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.
8
1.2. Programul High - 3D
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..
9
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.
10
- 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:
11
(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.
12
1.2.2. Structurile de date folosite
• 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 TPunct2 { int x,y; }; // o latură dintr-o secţiune are două puncte
struct TLatura { TPunct2 p1,p2; };
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:
13
• // 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;
14
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.
Pentru a explica cum se utilizează programul, vom considera două corpuri şi vă vom spune
cum se pot desena ele:
♦ 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 !
15
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:
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.
*/
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <dos.h>
16
begin asm { mov ax,1; int 33h } end
// Iniţilializare grafică:
procedure OpenGraph()
begin
struct TPunct2 { int x,y; }; // o latură dintr-o secţiune are două puncte
struct TLatura { TPunct2 p1,p2; };
17
int Z[NrMaxSectiuni+1];
// cota secţiunii curente şi cota secţiunii viitoare
int CotaCurenta, CotaViitoare;
// vectorul secţiunilor
TSectiune MSect[NrMaxSectiuni+1];
18
// 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
19
MouseShow(); // reafişarea cursorului de mouse
end
20
line(x1,y1,x1,y2); line(x1,y1,x2,y1);
end
end
21
// 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
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...
22
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
23
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
24
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
25
// 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
26
// 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
27
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
28
// sus-jos (pe Y), stânga-dreapta şi faţă-spate.
29
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
30
// î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
31
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
32
// Prima fază a programului: editarea corpului.
procedure EditareDesen()
begin
// iniţializări
SectCurenta=1;
CotaCurenta=-50; CotaViitoare=CotaCurenta;
coef=1;
AfisDateSectCurenta();
InitSectCurenta();
33
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;
34
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
35
36
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.
Iată, de exemplu, două cadre succesive (care nu diferă prea mult între ele) dintr-un fişier
FLI:
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:
37
byte mări nume semnificaţie
offset me
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.
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:
În mod curent, există cinci tipuri de chunck-uri pe care le puteţi vedea într-un fişier FLI:
38
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
39
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.
#include <dos.h>
#include <stdio.h>
#include <string.h>
// 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,®gss,®gss);
end
40
end
Byte expand[102];
char expandf[8];
unsigned long size, next, frit;
Word magic, frames, width, height, aux, flags, speed;
// 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);
41
end
42
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
43
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
Mult succes !
44
Capitolul 3. Afişări deosebite, folosind fişierele
de caractere
45
; 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>
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;
46
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;
#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;
47
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 );
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);
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 ){
48
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;
void main()
{
long length, current;
char *cptr;
STROKE *sptr;
49
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();
}
50
Capitolul 4. Despre jocurile pe calculator
Motto:
“Jocul este o punte aruncată
între copilărie şi vârsta matură” (J. Chateau)
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
51
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.
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.
52
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.
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.
53
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;
54
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;
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;
{$I RESIZE.INC}
begin
OpenGraph;
LoadPackBMPFile(0,0,'CARS.BMP');
55
ReadLn;
Mareste(0,0,40,50,7,5);
ReadLn;
Micsoreaza(0,0,40*7,50*5,7,2);
ReadLn;
CloseGraph
end.
program RedimensionareBMP256Culori;
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’.
56
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ă.
program TurnurileDinHanoi;
uses Crt;
57
const Pauza=50; forma='Û'; { #78 }
var Virf: array [1..3 ] of Byte;
58
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 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
59
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.
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.
60
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!
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;
61
procedure PrintAt(x,y: Integer; S: String);
begin
GoToXY(x,y); Write(S)
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;
62
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}
procedure MutaBila;
begin
MutaPaletele;
63
{ schimbare sens de deplasare }
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;
64
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;
Ş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:
65
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;
66
{ 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;
67
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.
program Navele;
uses Crt, MyCrt;
1
Pătruţ, Bogdan - Grafică în OOP… şi nu numai, Editura ADIAS, Râmnicu Vâlcea, 1995
68
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;
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
69
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);
70
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.
71
72
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.
73
Cele 7 tipuri de piese care apar în jocul de TETRIS
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.
74
Moment din desfăşurarea unui joc de TETRIS
unit utet;
interface
uses Graph,Crt;
procedure JocDeTetris(est,nord,NivelJoc: Integer;
var scor: Integer);
implementation
75
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;
76
SePoate := False;
{ într-una din noile coordonate cutia ar fi umplută }
SePoateMuta:=SePoate
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
77
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;
78
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
79
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 }
80
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 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;
81
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;
82
{ 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.
83
Î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:
program FeteDeOameni;
uses Crt, Graph, Graph1{, ViewBMP};
label sfirsit;
const lat=88; inalt=38;
NrTurnuri=4; NrFete=4; NrLinii=12;
SizeOfImage=22182;
84
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 }
85
' ');
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;
procedure InitTabla;
var i,j: Byte;
begin
86
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 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;
87
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;
88
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);
89
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.
90
(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 }
91
{ 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
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;
92
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;
93
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;
94
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;
95
end;
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;
96
var i: Byte;
begin
for i:=1 to D.NrSurf do
ModifySurface(D.Surf[i])
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.
97
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.
98
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.
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:
ş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ă.
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ă.
99
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:
î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:
Procedura de mai sus stă la baza procedurii care transformă un desen în altul, prezentată în
unit-ul uAnim.
100
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.
101
program Amestec;
uses Crt, MCGA, ViewBMP, uMouse;
const MaxNrBuc=20;
latime=250; inaltime=150;
autor: string = 'un joc de BOGDAN PATRUT';
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;
procedure Refacere;
var b,x,y: Integer;
begin
102
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
103
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.
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,
104
î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.
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 Deseneaza;
begin
case pas of
1: begin
CLine(10,180,100,180,12);
CRectangle(80,170,120,180,12)
end;
2: begin
105
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 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
106
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.
107
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:
Micsoreaza valoarea cotei viitoare cu 1.
###
Fişierul MY3D1.HLP: Comanda Z +
Mareste valoarea cotei viitoare cu 10 unitati.
###
### Comanda Z viitor
Comanda LINIE Stabileste valoarea cotei curente ca
Aceasta comanda va permite sa trasati fiind cota viitoare.
segmente de dreapta. Trasarea unui segment ###
se face selectând prin apasarea butonului Comanda Vizualizare
stâng al mouse-ului primul punct, iar apoi, Trece la in meniul de vizualizare si
tinând butonul apasat, deplasati mouse-ul rotatie a corpului
pâna va hotarâti unde sa fie cel de al doilea ###
punct. In acest moment dati drumul la buton EDITARE CORP
### Acest moment al programului editeaza corpul
Comanda DREPTUNGHI tridimensional folosind urmatoarea tehnica:
Aceasta comanda va permite sa trasati -se creeaza sectiuni paralele cu xOy;
dreptunghiuri, selectând cu butonul stâng al -aceste sectiuni difera prin z si ele se leaga
mouse-ului colturile stânga-sus si dreapta-jos prin punctele corespunzatoare;
ale dreptunghiului ce va fi trasat. Dupa ce -intr-o sectiune se deseneaza mai multe linii;
ati stabilit unul din colturi, tineti butonul -alegerea lui z viitor se face cu comenzile Z.
mouse-ului pâna va hotarâti la celalalt colt. Pentru detalii cititi documentatia.
Fireste, dreptunghiurile vor avea laturile Pt. a vedea corpul editat, apasati Viz.3D.
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).
### Fişierul MY3D2.HLP:
Comanda Mutare
Selectând cu mouse-ul o latura ###
(agatându-i mijlocul), aceasta se poate Comanda Unghi -
muta intr-o alta pozitie. Noua pozitie Este vorba de micsorarea cu 5 unitati
se stabileste când se da drumul la a unghiului de rotatie a corpului fata de cele
butonul din stânga al mouse-ului. trei axe Ox, Oy si Oz.
### Acest unghi este afisat in partea de jos
Comanda Coef - a ecranului.
Micsoreaza valoarea coeficientului valabil ###
pentru urmatoarea comanda Redim. . Comanda Unghi +
Micsorarea se face cu 0.1. Coeficientul este Este vorba de marirea cu 10 unitati
afisat in partea de jos a ecranului. a unghiului de rotatie a corpului fata de cele
Marirea sa se poate face cu Coef +. trei axe Ox, Oy si Oz.
### Acest unghi este afisat in partea de jos
Comanda Coef + a ecranului.
Mareste valoarea coeficientului valabil ###
pentru urmatoarea comanda Redim. . Comanda OX -
Marirea se face cu 0.5. Coeficientul este Aceasta comanda determina executarea unei
afisat in partea de jos a ecranului. rotatii cu un -unghi, fata de axa OX.
Micsorarea sa se poate face cu Coef -. Unghiul de rotatie este afisat in partea
### de jos a ecranului si poate fi modificat cu
Comanda Redim. comenzile Unghi - si Unghi +.
Aceasta comanda permite redimensionarea ###
figurii din sectiunea curenta cu coeficientul Comanda OX +
afisat in partea de jos a ecranului. Aceasta comanda determina executarea unei
Acesta poate fi modificat cu Coef - si Coef +. rotatii cu un unghi, fata de axa OX.
Comanda este necesara pentru crearea de Unghiul de rotatie este afisat in partea
corpuri de jos a ecranului si poate fi modificat cu
sun forma de trunchi, dar trebuie ca bazele sa comenzile Unghi - si Unghi +.
fie centrate in mijlocul ecranului. ###
### Comanda OY -
Comanda Z - Aceasta comanda determina executarea unei
108
rotatii cu un -unghi, fata de axa OY. cu comanda Salveaza, dintr-un fisier.
Unghiul de rotatie este afisat in partea Corpul poate fi vizualizat si rotit, nu
de jos a ecranului si poate fi modificat cu insa si modificat.
comenzile Unghi - si Unghi +. ###
### PROGRAM DE GRAFICA TRIDIMENSIONALA
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 - Realizat de Bogdan Patrut, 1994, 1995.
Aceasta comanda determina executarea unei
rotatii cu un -unghi, fata de axa OZ. Toate drepturile asupra acestui program
Unghiul de rotatie este afisat in partea apartin autorului. Modificarea, copierea
de jos a ecranului si poate fi modificat cu sau distribuirea acestui program fara
comenzile Unghi - si Unghi +. permisiunea scrisa a autorului sint strict
### interzise si vor fi pedepsite conform
Comanda OZ + legilor României.
Aceasta comanda determina executarea unei ###
rotatii cu un unghi, fata de axa OZ. PROGRAM DE GRAFICA TRIDIMENSIONALA
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.
### Pentru a obtine un ajutor ("help") la o
Comanda Salveaza anumita comanda, din oricare meniu, plasati
Corpul creat in acest meniu poate fi mouse-ul in dreptul acelei comenzi
salvat intr-un fisier, urmind ca el si actionati butonul din dreapta.
sa poata fi incarcat alta data, Se poate obtine ajutor si la editor, daca
pentru a fi vizualizat. actionati butonul din dreapta al mouse-ului
### in interiorul ferestrei de editare.
Comanda Incarca ###
Incarca un corp creat anterior si salvat
109
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;
110
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
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;
111
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 OpenGraph;
[ iniţializare grafică ]
begin
SetVideoMode($13);
_x_max_:=319; _y_max_:=219;
SetColor(15); WriteMode:=0
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 PrintC(a:char);
[ afişează în poziţia curentă a cursorului de scriere un caracter a ]
var c:integer;
112
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 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);
113
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;
114
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;
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;
implementation
var
testH: BitMapFileHeader;
testI: BitMapInfo;
cRed, cGreen, cBlue: byte;
cRGB: RGBQuad;
cReg: byte;
115
fBMP: file;
iBMP, jBMP: LongInt;
memBMP: byte;
readByte: byte;
LineBuff: array[0..639] of byte;
Error: Boolean;
116
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;
117
until jBMP < y1;
Close(fBMP);
end;
118
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;
119
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;
120
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.
121
Bibliografie
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.
122