Sunteți pe pagina 1din 122

Bogdan PĂTRUŢ Iulian Marius FURDU

Grafică 3D, animaţie şi jocuri

EduSoft 2005
Redactor: Tiberiu SOCACIU

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


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

I. Furdu, Iulian Marius

004.42C

Copyright © 2005 Editura EduSoft


Toate drepturile asupra prezentei ediţii sunt rezervate Editurii EduSoft.
Reproducerea parţială sau integrală a conţinutului, prin orice mijloc, fără
acordul scris al Editurii EduSoft este interzisă şi se va pedepsi conform
legislaţiei în vigoare.

Editura EduSoft
600065 Bacău, str. 9 Mai, nr. 82, sc. C, ap. 13
E-mail: 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

Grafică 3D, animaţie şi jocuri


surse de programe în C++ şi Pascal

Editura EduSoft
Bacău 2005

3
CUPRINS

Introducere 5

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


1.1. Translaţii. Rotaţii 7
1.2. Programul High – 3D 9
1.2.1. Bazele teoretice 9
1.2.2. Structurile de date folosite 13
1.2.3. Utilizarea programului 15
1.2.4. Listingul comentat al programului 16

Capitolul 2. Animaţie profesională 37


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

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


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

Capitolul 4. Despre jocurile pe calculator 51


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

Capitolul 5. Jocuri folosind unit-ul CRT 57


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

Capitolul 6. Jocuri folosind unit-ul GRAPH 73


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

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


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

Anexa 1. Fişierele de ajutor ale programului High-3D 108

Anexa 2. Uniturile uMouse, MCGA şi ViewBMP 110

Bibliografie 122

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

1.1 Translaţii. Rotaţii.


În principiu, ne propunem să realizăm un program pe calculator, care să editeze grafic un corp,
pe care apoi să-l proiecteze în plan, să-l vizualizeze şi să-l rotească sub diferite unghiuri, faţă de una
din cele trei axe de coordonate, prin punctul de coordonate (0,0,0).
Să considerăm, aşadar un sistem de coordonate spaţiale, OXYZ şi un punct (x,y,z). Dacă dorim
să realizăm translaţii şi rotaţii ale acestui punct, vom folosi o formulare matricială, în care matricea
(x1,y1,z1,1) a punctului obţinut în urma aplicării unei transformări, să se obţină din matricea punctului
iniţial (x,y,z), înmulţită matricial cu o matrice de dimensiune 4 x 4. A patra componentă (1), apare
pentru uşurinţa calculelor.

• Astfel, dacă dorim să translatăm punctul de coordonate (x,y,z) într-un nou punct (x1,y1,z1),
transformarea ce trebuie aplicată este:

1 0 0 0
(x1,y1,z1) = (x,y,z,1) 0 1 0 0
0 0 1 0
Tx Ty Tz 1 ,
unde Tx, Ty şi Tz sunt componente ale translaţiei în direcţiile X, Y şi Z, adică deplasările pe cele trei
direcţii.

• Transformările rotaţiei în spaţiul tridimensional sunt mai complexe, fiind necesară determinarea
unei axe de rotaţie. Specificarea axei de rotaţie include atât direcţia cât şi localizarea. Este necesar să se
definească rotaţiile în raport cu cele trei axe ale reperului Ox, Oy, Oz.
Rotaţia în jurul axei Ox prin punctul (0,0,0) este:
1 0 0 0
(x1,y1,z1,1) = (x,y,z,1) 0 cos a sin a 0
0 -sin a cos a 0
0 0 0 1

Din egalarea celor doi membri şi identificarea necunoscutelor obţinem formulele:


x1 = x, y1 = y cos a - z sin a şi z1 = y sin a + z cos a.
Unghiul de rotaţie a este măsurat în sens orar în jurul originii, privind originea dintr-un punct
de pe axa X pozitivă. Coordonata x nu este, evident, afectată.

În mod similar se obţin formulele pentru rotaţii în jurul celorlalte două axe.

• Dar, înainte de a-l roti, un corp va trebui să fie reprezentat planar, pe ecran. Coordonatele ecranului,
fiind numere întregi originea fiind în colţul stânga-sus (abscisa crescând de la stânga la dreapta, iar
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.

Avem un şir de rapoarte egale, din care obţinem:

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

ymax = d(O,Q), adică distanţa dintre observator şi punctul Q, corespunzător punctului R,


(xr,yr,zr) = coordonatele lui R faţă de sistemul de coordonate tridimensional dat, iar (a,b) sunt
coordonatele proiecţiei sale (deci ale lui P), în planul bidimensional, situat între ochiul observatorului şi
punctul R.

Formulele prezentate anterior se regăsesc, cu adaptările de rigoare, în cadrul funcţiilor


RotireOX, respectiv ProiecteazăCorpul din programul de care se ocupă capitolul următor.

8
1.2. Programul High - 3D

În urma unei munci susţinute, de cercetare, proiectare şi programare, în perioada septembrie


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

1.2.1. Bazele teoretice


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

Astfel, pentru a construi un corp vom desena diferite secţiuni xOy, plimbându-ne de-a lungul
lui Oz şi unind punctele corespunzătoare între ele: 1 cu 1', 2 cu 2' etc..

Probleme apar atunci când se doreşte crearea unor corpuri, ca de exemplu:

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.

Pentru obţinerea unei piramide se va proceda astfel:


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

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

În programul HIGH-3D am definit următoarele tipuri de date:

• TPunct - pentru a reprezenta punctele din spaţiu, fiecare punct având specificate cele trei
coordonate ale sale;
• TCoord - pentru reprezentarea punctelor din plan, date prin doar două coordonate;
• TLinie - pentru reprezentarea unei linii, dată prin numerele de ordine ale punctelor extremităţi;
• TPunct2 - pentru a reprezenta puncte din plan, dar din cadrul secţiunilor corpului editat;
• TLatura - pentru reprezentarea acelor laturi din fiecare secţiune a corpului;
• TSecţiune - pentru a reprezenta ansamblul acestor laturi precum şi numărul lor, într-o secţiune;

Aceste tipuri de date sunt definite după cum urmează. De asemenea, am dat şi lista
constantelor şi variabilelor globale folosite în reprezentările grafice din program. Astfel, punctele
corpului sunt memorate în “mulţimea” de puncte MPuncte, iar liniile ce le unesc sunt date în
MLinie. Cotele secţiunilor sunt toate memorate în vectorul Z. MPro este mulţimea punctelor
proiecţie. MLinie vor uni aceste puncte, corespunzător cu MPuncte. Astfel, dacă MPuncte[i] şi
MPuncte[j] sunt unite, atunci şi MPro[i] şi MPro[j] vor fi unite.

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

struct TPunct { int x,y,z; } MPuncte[NrMaxPuncte+1];


// punct 3D
struct TCoord { int a,b; } MPro[NrMaxPuncte+1];
// punct de proiecţie
// linie între două puncte numerotate cu p şi q
struct TLinie { int p,q; } MLinii[NrMaxLinii+1];

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

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


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

// secţiunea în care se desenează


int SectCurenta;
// cotele tuturor secţiunilor
int Z[NrMaxSectiuni+1];
// cota secţiunii curente şi cota secţiunii viitoare
int CotaCurenta, CotaViitoare;
// vectorul secţiunilor
TSectiune MSect[NrMaxSectiuni+1];

// numărul de puncte ale corpului, precum şi numărul de linii


// care leagă aceste puncte între ele, doua câte două:
int NrPuncte, NrLinii;

Un experimentat în programare, având un stil de lucru cât mai elegant, va înţelege uşor
motivul definirii structurii de meniu ca mai jos:
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.

Fiecare metodă de editare tridimensională are şi avantaje şi dezavantaje. Această metodă, în


afara faptului că este facil de implementat, prezintă avantajul unei foarte simple creări a corpurilor
cu anumite simetrii, dacă respectivul corp este bine imaginat.

Pentru a explica cum se utilizează programul, vom considera două corpuri şi vă vom spune
cum se pot desena ele:

♦ O masă dreptunghiulară, cu patru picioare prismatice:


Nimic mai simplu. Se desenează un dreptunghi centrat în centrul ecranului. (Tehnica de
desenare a unui dreptunghi o presupunem cunoscută. Dacă nu, ea se poate deduce din procedura din
program.).
Apoi se măreşte cota Z, apăsând de mai multe ori pe “Z+”. Se trece la o nouă cotă, apăsând
“Schimbă Z”. În acest moment o nouă secţiune, vidă, ia naştere. Se copiază în această secţiune,
prima secţiune. Deci se va apăsa “Copie”. Acum se acţionează “Schimbă Z” din nou. Se suprascrie,
în acest fel, o a treia secţiune, peste cea de a doua, dar având aceeaşi cotă cu ea. Aici se desenează,
din linii, patru poligoane regulate, în cele patru colţuri ale dreptunghiului. Se trece (cu “Z+”, de
câteva ori, urmat de “Schimbă Z”) într-o nouă secţiune şi se dă comanda “Copie”. Masa e gata.
Pentru a o vizualiza se acţionează butonul corespunzător.
Acum deja suntem în alt meniu. Vedem masa de pe direcţia axei Y. Axa Z este sus-jos, iar
axa X este stânga-dreapta. Imaginea corpului nu ne satisface, aşa că începem să îl rotim, cu
comenzile în cauză. Unghiul de rotaţie se schimbă cu “Unghi -” şi “Unghi +”. Dacă nu ştim ceva,
putem acţiona butonul din dreapta în dreptul comenzilor şi obţinem “help”. Corpul poate fi salvat,
se poate reveni în meniul de editare, iar altă dată corpul va putea fi restaurat, din fişierul High-3D.

♦ Un pahar cu picior.
Exemplul e clasic şi e împrumutat de la VU-3D. Se desenează cercuri, care se copiază unul
după altul, redimensionându-l, pentru a forma mai întâi baza piciorului (două cercuri cu raze
suficient de mari), apoi piciorul (două cercuri, mai mici, dar mai distanţate între ele), apoi un cerc
ceva mai mare şi altul şi mai mare decât baza piciorului, distanţate şi ele, pentru a forma paharul
propriu-zis.

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

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:

bgiobj egavga.bgi egavga.obj [ _EGAVGA_driver ]

numele nume nume numele public al driverului grafic


programului driver fişier care se va afla în EGAVGA.OBJ
de conversie grafic obiect (poate lipsi, acesta e implicit)

Se creează apoi un proiect (HIGH3D.PRJ), cu Open project... (din meniu), în care se pune acest fişier
(HIGH3D.CPP) şi fişierul EGAVGA.OBJ şi se apasă F9, pentru Make. Programul va putea fi rulat cu Ctrl-F9 sau
din DOS, cu HIGH3D, chiar dacă fişierul EGAVGA.BGI nu ar exista.
*/

// Se includ acele headere care sunt necesare în program.

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

// Definiţii pentru similitudini cu limbajul Pascal:

#define procedure void


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

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


#define inaltime 15

// Distanţa observatorului faţă de corpul vizualizat:


int ymax = 14000;

// Coordonatele centrului ecranului:


int Xmij,Ymij;

// Funcţii assembler de lucru cu mouse-ul:


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

// Afişarea cursorului de mouse


procedure MouseShow()

16
begin asm { mov ax,1; int 33h } end

// Ascunderea cursorului de mouse


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

// Citirea stării mouse-ului:


// in b = butonul (0 = nici un buton, 1 = stinga, 2 = dreapta, 3 = ambele
procedure MouseData(int * b, int * x, int * y)
begin
int bb,xx,yy;
asm { mov ax,3; int 33h; mov bb,bx; mov xx,cx; mov yy,dx }
*b = bb; *x = xx; *y = yy;
end

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


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

// Iniţilializare grafică:
procedure OpenGraph()
begin

if (registerbgidriver(EGAVGA_driver) < 0) then


begin
printf("Eroare grafic† !");
return;
end
int gd=DETECT, gm;
initgraph(&gd,&gm,""); // calea grafică
end

// Definirea structurii de meniu:


const MaxCom = 12; // numărul maxim de comenzi din meniuri
struct TMeniu {
int NrCom; // numărul de comenzi ale meniului
char * NumeCom[MaxCom+1]; // numele acestora
};
// constantă reprezentând neselectarea vreunei comenzi din meniuri
const cmNimic=0;

// structurile de date folosite în reprezentarea corpului:


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

struct TPunct { int x,y,z; } MPuncte[NrMaxPuncte+1]; // punct 3D


struct TCoord { int a,b; } MPro[NrMaxPuncte+1]; // punct de proiecţie
// linie între două puncte numerotate cu p şi q
struct TLinie { int p,q; } MLinii[NrMaxLinii+1];

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

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


struct TSectiune {
int NrLaturi;
TLatura Latura[NrMaxLatS+1];
};
// secţiunea în care se desenează
int SectCurenta;
// cotele tuturor secţiunilor

17
int Z[NrMaxSectiuni+1];
// cota secţiunii curente şi cota secţiunii viitoare
int CotaCurenta, CotaViitoare;
// vectorul secţiunilor
TSectiune MSect[NrMaxSectiuni+1];

// numărul de puncte ale corpului, precum şi numărul de linii


// care leagă aceste puncte între ele, doua câte două:
int NrPuncte, NrLinii;
// pentru reprezentarea tridimensională, avem aceasta variabilă
// Axe3D care indică dacă se trasează şi axele sau nu, precum şi
// variabila Unghi, a cărei valoare indică unghiul pentru rotiri:
int Axe3D, Unghi;
// pentru editarea corpului avem un coeficient de redimensionare:
double coef;

// comenzile actuală şi veche alese din meniu:


int Comanda, ComandaVeche;

// funcţie necesară ieşirii forţate din program,


// la apăsarea tastei Esc
int SeApasaEscape()
begin
if (kbhit()) then
begin
if (getch() == 27) then
begin closegraph(); exit(1); return 1; end
else return 0;
end
else return 0;
end

// Urmează funcţii de interes general:

// funcţie care verifică apartenenţa unui punct (x,y)


// la interiorul unui dreptunghi, având colţul stânga-sus (x1,y1) şi
// colţul dreapta-jos (x2,y2); se foloseşte pentru meniuri
int Apartine(int x,int y, int x1, int y1, int x2, int y2)
begin
return ((x1<=x) and (x<=x2) and (y1<=y) and (y<=y2));
end

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


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

// minimul a două numere întregi


int min(int x,int y)
begin
if (x<y) then return x; else return y;
end
// emiterea unui semnal sonor
procedure Beep()
begin
sound(300); delay(20);
sound(350); delay(20); nosound();
end

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

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


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

// Procedura de afişare de informaţii referitoare la


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

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

// o procedură similară pentru zona de stare din


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

// primul meniu, având 10 comenzi, după cum urmează:


TMeniu Unu = {11,{" ","Linie","Drept.","Copie","Mutare",
"Coef. -","Coef. +","Redim",
"Z -","Z +","Alege Z","Viz. 3D",""
} };
// constante simbolice asociate comenzilor din meniu:
const cmLinie=1; const cmDreptunghi=2; const cmCopie=3;
const cmMutare=4;
const cmCoefM=5; const cmCoefP=6; const cmRedim=7;
const cmPrevZ=8; const cmNextZ=9; const cmChangeZ=10;
const cmView=11;

// al doilea meniu, de vizualizare, cu 11 comenzi


TMeniu Doi = {11,{" ","Unghi -","Unghi +","OX -","OX +",
"OY -","OY +","OZ -","OZ +","Desen",
"Salvez","Incarc"
} };
// constantele simbolice asociate acestuia
const cmUnghiM=1; const cmUnghiP=2; const cmOXM=3; const cmOXP=4;
const cmOYM=5; const cmOYP=6; const cmOZM=7; const cmOZP=8;
const cmDesen=9; const cmSave=10; const cmLoad=11;

// desenarea unui buton din meniu


procedure Buton(int x1, int y1, int x2, int y2, char * nume, int activ)
begin
setfillstyle(SOLID_FILL,LIGHTGRAY);
bar(x1,y1,x2,y2);
if (activ) then
begin
setcolor(BLUE);
outtextxy(x1 + (x2-x1) / 2 + 2,
y1 + (y2-y1) / 2 + 2, nume);
setcolor(MAGENTA);
rectangle(x1,y1,x2,y2);
end
else begin
setcolor(MAGENTA);
outtextxy(x1 + (x2-x1) / 2,
y1 + (y2-y1) / 2, nume);
line(x2,y1,x2,y2); line(x1,y2,x2,y2);
setcolor(WHITE);

20
line(x1,y1,x1,y2); line(x1,y1,x2,y1);
end
end

// afisarea unui meniu M, cu numărul NrMeniu


procedure Meniu(TMeniu M, int NrMeniu, int tip)
begin
int i;
MouseHide();
// afişarea stării în zona de jos a ecranului,
// în funcţie de numărul meniului
if (NrMeniu==1) then AfisDateSectCurenta();
else AfisDateCorp();
// se calculează lăţimea unei comenzi din meniu
int lat = getmaxx() / M.NrCom;
settextjustify(CENTER_TEXT, CENTER_TEXT);
// pentru fiecare comandă i se desenează un dreptunghi,
// care este alb pt. comanda curentă, în rest galben
// şi în care se scrie numele comenzii M.NumeCom[i]
if (tip == 2) then
for (i = 1; i <= M.NrCom; i++)
Buton(lat*(i-1),0,lat*i-2,
inaltime,M.NumeCom[i],i==Comanda);
else
Buton(lat*(Comanda-1),0,lat*Comanda-2,
inaltime,M.NumeCom[Comanda],tip);
MouseShow();
end

// când se trece la o nouă secţiune,


// aceasta trebuie să fie iniţializată
procedure InitSectCurenta()
begin
MouseHide();
MSect[SectCurenta].NrLaturi = 0; // nu are nici o latură
Z[SectCurenta] = CotaCurenta; // se stabileşte cota sa
clearviewport(); // se şterge ecranul, având fosta secţiune
Meniu(Unu,1,2); // se afişează primul meniu
MouseShow();
end

// secţiunea curentă trebuie actualizată la fiecare trasare


// a unei noi figuri între (px,py) şi (ux,uy), fie că aceasta
// este dată fie de o linie, fie de 4 linii, în cazul unui dreptunghi
procedure ActualizeazaSectCurenta(int px, int py, int ux, int uy)
begin
int N; // variabilă locală pt. uşurinţa scrierii
// actualizarea se face în funcţie de comanda de desenare
switch (Comanda)
begin
case cmLinie: begin
// numărul de laturi din secţiunea
// curentă creşte cu o unitate
MSect[SectCurenta].NrLaturi++;
N = MSect[SectCurenta].NrLaturi;
// se adaugă această latură
MSect[SectCurenta].Latura[N].p1.x = px;
MSect[SectCurenta].Latura[N].p1.y = py;
MSect[SectCurenta].Latura[N].p2.x = ux;
MSect[SectCurenta].Latura[N].p2.y = uy;
end break; // linie
case cmDreptunghi: begin
N = MSect[SectCurenta].NrLaturi;

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

// la apăsarea butonului 2 se poate obţine un text ajutător,


// citit dintr-un fişier text .HLP
// procedura se apelează în funcţie de numărul meniului şi
// poziţia comenzii din meniul respectiv
procedure Help(int Pozitie, int NrMeniu)
begin
// textul se va afişa într-o zonă dreptunghiulară,
// din mijlocul ecranului, de lăţime 2*lx şi înălţime 2*ly
const lx=190; const ly=80;
char sir[1]; // în şir avem 1 sau 2, nr. meniului
char sirul[12]; // aici ţinem numele fişierului întreg
int cc; // culoarea curentă, care va fi salvată
FILE * F; // fişierul F de unde se va citi
char rind[80]; // rândul citit e un şir de maxim 80 caractere
int p; // poziţia în fişier, a textului ce va fi afişat
int py; // py = poziţia pe verticală, de afişare
int b,x,y; // buton şi coordonate mouse
unsigned int Size; // mărimea imaginii de sub "help" şi
void * Imag; // zona de memorie unde va fi păstrată

MouseHide();
cc = getcolor();
itoa(NrMeniu,sir,10); // determină numele fişierului de "help"
strcpy(sirul,"MY3D");
strcat(sirul,sir); strcat(sirul,".HLP");
// se încearcă deschiderea fişierului pentru citire în modul text
if ((F=fopen(sirul, "rt")) != NULL)
begin
// dacă operaţia reuşeşte
p = 0;
// se caută textul, sărind peste un număr de Pozitie şiruri ###
while ((not feof(F)) and (p < Pozitie))
begin
fgets(rind,80,F);
if (!strcmp(rind,"###\n")) then p++;
end
// când se gaseşte...

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

// se stabileşte comanda curentă din meniul M, cu numărul NrMeniu

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

// această procedură desenează o linie între două puncte


// folosind mouse-ul; tehnica de desenare este următoarea:
// se fixează un punct (în interiorul zonei de desenare permise),
// după care, în modul de scriere Xor, se trasează (de două ori)
// o linie între primul punct fixat şi punctul curent, până ne
// hotărâm la punctul final
procedure DeseneazaLinie()
begin
int b,px,py,x,y; // (px,py) = primul punct, (x,y) = al doilea
MouseData(&b,&px,&py); // Unde este mouse-ul ?
// S-a acţionat butonul stâng şi suntem în zona de desenare ?
if ((Apartine(px,py,0,inaltime+1,getmaxx(),
getmaxy()-inaltime-1))
and (b==1)) then
begin
// Dacă da, alegem acest mod de scriere şi ascundem mouse-ul:
setwritemode(XOR_PUT);
MouseHide();
do { // Care sunt noile coordonate ale mouse-ului ?
MouseData(&b,&x,&y);
// Sunt ele valide ? Dacă nu se forţează la limite.
if (y<inaltime+1) then
begin
// limita de sus (sub meniu)
y = inaltime+1;
MouseMove(x,y); // acolo se mută mouse-ul
end
if (y>getmaxy()-inaltime-1) then
begin
// limita de jos (peste zona de stare)
y = getmaxy()-inaltime-1;
MouseMove(x,y);
end
line(px,py,x,y); // Se trasează linia, apoi...
// ...se arată mouse-ul şi linia, puţin timp,

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

// O procedură similară realizează trasarea unui dreptunghi,


// după aceeaşi tehnică; dreptunghiul este determinat unic
// de două colţuri opuse: (px,py) şi (x,y);
// la sfârşit SectCurenta se actualizează cu 4 laturi.
procedure DeseneazaDreptunghi()
begin
int b,px,py,x,y;
MouseData(&b,&px,&py);
if (Apartine(px,py,0,inaltime,getmaxx(),getmaxy()))and(b==1))
then begin
setwritemode(XOR_PUT);
MouseHide();
do {
MouseData(&b,&x,&y);
if (y<inaltime+1) then
begin
y = inaltime+1;
MouseMove(x,y);
end
if (y>getmaxy()-inaltime-1) then
begin
y = getmaxy()-inaltime-1;
MouseMove(x,y);
end
rectangle(px,py,x,y);
MouseShow(); delay(50); MouseHide();
rectangle(px,py,x,y);
} while (b != 0);
rectangle(x,y,px,py);
setwritemode(COPY_PUT);
MouseShow();
ActualizeazaSectCurenta(px,py,x,y);
end
end

// verifică dacă punctul de coordonate (x,y) este


// în apropierea mijlocului laturii L
int Aproape(int x, int y, TLatura L)
begin
int eps=5;
int xm = (L.p1.x + L.p2.x)/2;
int ym = (L.p1.y + L.p2.y)/2;
if ((abs(x-xm) < eps) and (abs(y-ym) < eps)) then
return 1;
else return 0;
end

// procedura de mutare în altă poziţie

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

// Această procedură copiază conţinutul fostei secţiuni în secţiunea


// curentă, renunţând la conţinutul acesteia din urmă.
procedure CopiazaDinSectVeche()
begin
int i;
int SC = SectCurenta;
MouseHide();
Beep(); // se avertizeaza sonor operatia
// În cazul în care se doreşte acest lucru pentru prima
// secţiune, atunci se şterge pur şi simplu secţiunea
if (SectCurenta == 1) then
begin
MSect[SectCurenta].NrLaturi = 0;
clearviewport(); Meniu(Unu,1,2);
end
else // altfel se face copierea din MSect[SC-1] în MSect[SC]
// SC = SectCurenta
begin

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

// În funcţie de coef, laturile din secţiunea curentă pot fi


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

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

// Această procedură salvează corpul editat într-un fişier,


// cu numele NumeFis. Apelul în program se face cu CORP.3D.
// Propunem cititorului îmbunătăţirea programului pentru a permite
// crearea de fişiere cu nume date de utilizator.
procedure SalveazaCorpul(char * NumeFis)
begin
FILE * F; // fisierul binar
int i; // i = punct curent
if ((F=fopen(NumeFis, "wb")) != NULL)
begin
// Se deschide, pentru scriere binară, fişierul F,
// se scrie în el numărul de puncte şi numărul de linii
// ale corpului...
fwrite(&NrPuncte, sizeof(NrPuncte), 1, F);
fwrite(&NrLinii, sizeof(NrLinii), 1, F);
// ...apoi se salvează punctele...
for (i=1; i <= NrPuncte+4; i++)
fwrite(&MPuncte[i], sizeof(MPuncte[i]), 1, F);
// ...şi liniile
for (i=1; i <= NrLinii+3; i++)
fwrite(&MLinii[i], sizeof(MLinii[i]), 1, F);
// se închide fişierul.
fclose(F);
end
end

// Inversa procedurii de mai înainte este procedura...


procedure IncarcaCorpul(char * NumeFis)
begin
FILE * F; int i, Lat;
if ((F=fopen(NumeFis, "rb")) != NULL)
begin
// ... care citeşte numărul de puncte...
fread(&NrPuncte, sizeof(NrPuncte), 1, F);
// ... si de linii
fread(&NrLinii, sizeof(NrLinii), 1, F);
// ... apoi toate punctele şi toate liniile
for (i=1; i <= NrPuncte+4; i++)
fread(&MPuncte[i], sizeof(MPuncte[i]), 1, F);
for (i=1; i <= NrLinii+3; i++)
fread(&MLinii[i], sizeof(MLinii[i]), 1, F);
fclose(F);
end
end

// Cel de al doilea meniu permite vizualizarea, dar şi rotirea


// corpului creat în prima fază. Rotirea se face cu un unghi
// exprimat în grade (număr întreg, negativ sau pozitiv).
// Există trei feluri de rotire, după cele trei axe: OX, OY şi OZ.
// De fapt, se realizează rotiri ale ochiului observatorului pe
// o sferă centrată în (0,0,0) (care este mai aproape de corp),
// sfera de rază ymax, în cele trei direcţii:

28
// sus-jos (pe Y), stânga-dreapta şi faţă-spate.

// Rotirea corpului după OX presupune recalcularea geometrică


// a coordonatelor corpului, precum şi a punctelor care determină axele
procedure RotireOX(int alfa)
begin
int i; float beta; // unghiul exprimat în radiani
int aux1,aux2;
beta = alfa*M_PI/180;
for (i=1; i <= NrPuncte+4; i++) // !!
begin
aux1 = Round(MPuncte[i].y*cos(beta)
-MPuncte[i].z*sin(beta));
aux2 = Round(MPuncte[i].y*sin(beta)+
MPuncte[i].z*cos(beta));
MPuncte[i].y = aux1; MPuncte[i].z = aux2;
end
end
// La fel şi în cazul celorlalte două rotiri: după OY...
procedure RotireOY(int alfa)
begin
int i; float beta; int aux1,aux2;
beta = alfa*M_PI/180;
for (i=1; i <= NrPuncte+4; i++) // !!
begin
aux1 = Round(MPuncte[i].x*cos(beta)
-MPuncte[i].z*sin(beta));
aux2 = Round(MPuncte[i].x*sin(beta)
+MPuncte[i].z*cos(beta));
MPuncte[i].x = aux1; MPuncte[i].z = aux2;
end
end
// ... şi după OZ.
procedure RotireOZ(int alfa)
begin
int i; float beta; int aux1,aux2;
beta = alfa*M_PI/180;
for (i=1; i <= NrPuncte+4; i++) // !!
begin
aux1 = Round(MPuncte[i].x*cos(beta)-
MPuncte[i].y*sin(beta));
aux2 = Round(MPuncte[i].x*sin(beta)+
MPuncte[i].y*cos(beta));
MPuncte[i].x = aux1; MPuncte[i].y = aux2;
end
end

// Corpul memorat prin puncte cu trei cooordonate şi linii


// ce unesc aceste puncte, două câte două, trebuie proiectat
// pe ecran, în planul 2D, pentru a putea fi vizualizat.
// Vectorul MPro conţine toate coordonatele proiecţiilor
// tuturor celor NrPuncte puncte. Se ţine cont de distanţa
// ymax de unde priveşte observatorul.
procedure ProiecteazaCorpul()
// Obţin în MPro imaginea pe ecran a lui MPct.
begin
int i, yr;
double xr, aa; // Mare bătaie de cap cu împărţirile astea ! ...
// Pentru a nu avea probleme cu erorile de calcul,
// se foloseşte tipul double şi conversia la el a întregilor...
for (i=1; i <= NrPuncte+4; i++)
// se continuă cu cele trei axe OX, OY şi OZ
begin

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

// După ce a fost proiectat, corpul păstrează legăturile date de MLinii


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

// Această procedură desenează "vag" ce a fost creat

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

// Următoarele două proceduri sunt necesare unor comenzi din


// ambele meniuri, comenzi care au o acţiune imediată şi nu
// trebuie să lase valoarea variabilei Comanda setată pe ele.
// De asemenea, aceste proceduri actualizează zona de stare.

procedure Revino1() // valabilă pt. primul meniu


begin
AfisDateSectCurenta();
Meniu(Unu,1,1);
delay(100); // temporizare necesară eliberării butonului.
Meniu(Unu,1,0);
Comanda = cmNimic;
end

procedure Revino2() // valabilă pentru cel de al doilea meniu.


begin
AfisDateCorp();
Meniu(Doi,2,1);
delay(100);
Meniu(Doi,2,0);
Comanda = cmNimic;
end

// Urmează o procedură care execută comanda curentă, în funcţie


// de meniul din care a fost selectată.
procedure ExecutaComanda(int NrMeniu)
begin
if (NrMeniu == 1) then // primul meniu: editare
switch (Comanda)
begin
case cmLinie: DeseneazaLinie(); break;
case cmDreptunghi:
DeseneazaDreptunghi(); break;
case cmCopie: begin
CopiazaDinSectVeche();
Revino1();
end break;
case cmMutare: MutareLinie(); break;
case cmCoefM: begin // scăderea coeficientului
// de redimensionare
coef = coef - 0.1;
Revino1();
end break;
case cmCoefP: begin // creşterea sa

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();

// Se execută comenzi, până se apasă Esc sau se trece


// în cea de a doua fază, cu comanda de Vizualizare
Comanda=cmNimic;
ComandaVeche=cmNimic;
do {
StabilesteComanda(Unu,1);
if ((Comanda != ComandaVeche) and
(Comanda != cmNimic)) then
begin Meniu(Unu,1,2); ComandaVeche=Comanda; end
ExecutaComanda(1);
} while (!((Comanda == cmView) or (SeApasaEscape())));
MouseHide(); clearviewport(); MouseShow();
end

// Dar, înainte de a trece în cea de a doua fază, se apelează această procedură.


// Ea transformă MSect în MPuncte şi MLinii, creând puncte
// corespunzătoare punctelor din fiecare secţiune şi unindu-le între ele,
// apoi unind punctele din secţiuni consecutive, două câte două,
// corespunzător.
procedure TransformaSectiuni()
begin
int Sec,Lat, PuncteUnite;
// Este necesar un vector care să memoreze numărul de puncte unite
// din cadrul fiecărei secţiuni în parte.
int NrPcteUnite[NrMaxSectiuni+1];
NrLinii = 0; NrPuncte = 0; PuncteUnite = 0;
NrPcteUnite[0] = 0;
for (Sec=1; Sec <= SectCurenta; Sec++)
begin
NrPcteUnite[Sec]=0;
for (Lat=1; Lat <= MSect[Sec].NrLaturi; Lat++)
begin
NrLinii++;
PuncteUnite++; NrPcteUnite[Sec]++;
// Conversie ...
MPuncte[PuncteUnite].x =
XReal(MSect[Sec].Latura[Lat].p1.x);
MPuncte[PuncteUnite].y =
YReal(MSect[Sec].Latura[Lat].p1.y);
MPuncte[PuncteUnite].z = Z[Sec];
MLinii[NrLinii].p = PuncteUnite;
MLinii[NrLinii].q = PuncteUnite+1;
// nu e vecină cu latura următoare
if ((Lat == MSect[Sec].NrLaturi) or
( (Lat+1 <= MSect[Sec].NrLaturi) and
(!( ( MSect[Sec].Latura[Lat].p2.x ==
MSect[Sec].Latura[Lat+1].p1.x )
and
( MSect[Sec].Latura[Lat].p2.y ==
MSect[Sec].Latura[Lat+1].p1.y )
) ) ))
then
begin

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.

Programele profesionale de animaţie, cum este Autodesk Animator, prezintă un moment de


animaţie prin suprapunerea mai multor cadre (frame-uri), unul după altul. Acestea se citesc de pe
disc, dintr-un fişier cu extensia FLI (sau, mai nou, FLC), care se numeşte “flic” şi care are memorat
paleta de culori folosite, precum şi primul cadru din animaţie, apoi, pentru celelalte, diferenţele faţă
de cadrul precedent.
În cele ce urmează ne propunem să prezentăm structura fişierelor FLI folosite de Autodesk
Animator, precum şi un program exemplu de vizualizare a unor astfel de flic-uri, scris în Borland C.

2.1. Fişierele de animaţie FLI


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

Iată, de exemplu, două cadre succesive (care nu diferă prea mult între ele) dintr-un fişier
FLI:

Două cadre succesive dintr-un flic

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

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.

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

byte mări nume semnificaţie


offset me
0 4 size Numărul de bytes în cadre. Pentru Autodesk Animator acesta
este sub 64K.
4 2 magic Întotdeauna hexazecimal F1FA.
6 2 chunks Numărul de 'chunks' (“hălci”) în cadru.
8 8 expand Spaţiu pentru dezvoltări ulterioare. Toate zerouri.

După header-ul cadrului vin “grupele” (chuncks) care formează cadrul. Mai întîi vine un
chunk de culoare dacă paleta de culori s-a schimbat faţă de ultimul cadru. Apoi urmează un chunck
de pixeli, dacă pixelii s-au schimbat. Dacă acest cadru este absolut identic cu precedentul, atunci nu
va fi nici un chunck.

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

byte mări nume semnificaţie


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

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

număr nume semnificaţie


11 FLI_COLOR Harta compresată a culorilor (paleta de culori).
12 FLI_LC Linie compresată - cea mai întîlnită formă de compresie
pentru orice cadru, mai puţin pentru primul. Descrie
diferenţele de pixeli faţă de cadrul precedent.

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.

2.2. Programul PLFLI pentru animat fişiere FLI


Programul foloseşte unele definiţii date în header-ul PASCAL.H pe care l-am folosit şi în
programul de grafică tridimensională.
// PLFLI - Program de vizualizare (playback) a unui flic (fişier FLI)
// by Bogdan Pătruţ, Bacău, 1995

#include <dos.h>
#include <stdio.h>
#include <string.h>

// Definiţii pentru similitudini cu limbajul Pascal:


#define procedure void
#define begin {
#define end }
#define then

typedef unsigned char Byte;


typedef unsigned int Word;

// Tipul de Chunck
#define FLI_COLOR 11
#define FLI_LC 12
#define FLI_BLACK 13
#define FLI_BRUN 15
#define FLI_COPY 16

procedure SetColorRegister
(Byte RegColor, Byte RedValue, Byte GreenValue, Byte BlueValue)
begin
union REGS reggss;
reggss.h.ah=0x10;
reggss.h.al=0x10;
reggss.x.bx=RegColor;
reggss.h.ch=GreenValue;
reggss.h.cl=BlueValue;
reggss.h.dh=RedValue;
int86(0x10,&reggss,&reggss);
end

procedure SetVideoMode(Byte VideoCode)


begin
union REGS reggss;
reggss.h.al=VideoCode;
reggss.h.ah=0;
int86(0x10,&reggss,&reggss);

40
end

procedure Plot(int x, int y, Byte c)


begin
Byte Pixel=c;
int X=x, Y=y;
// Se poate mai simplu cu pokeb(0xA000,x+320*y,c),
// dar acest Plot este valabil şi pentru rezoluţia 800x600x256, de exemplu.;
asm begin
MOV AL,Pixel;
MOV AH,0CH;
MOV CX,X;
MOV DX,Y;
INT 10H;
end
end

int main(int argc, char **argv)


// argumentele liniei de comandă
begin
FILE *fis;

Byte expand[102];
char expandf[8];
unsigned long size, next, frit;
Word magic, frames, width, height, aux, flags, speed;

unsigned long sizef;


Word magicf, chuncks;

// CHUNCK
unsigned long sizec;
char ty1, ty2;

Byte pix;
int px;

// FLI_COLOR
Word numpackets;
Byte colskip, colchange, red, green, blue;
int colc;

// FLI_LC
Word numlines, changelines;
Byte numpack;
Byte skip_count;
char size_count;

// FLI_BRUN
Byte nrpack;
int szc, skc;
int xx, yy;

char nume_fli[12];
fpos_t poz_fis;

if (argc == 2) then
strcpy(nume_fli,argv[1]);
else
begin
fprintf(stderr,
"Programul se utilizeaza cu PFLI <nume.fli> .\n");
fprintf(stderr, "Dati <nume.fli> : ");
fscanf(stdin,"%s",&nume_fli);

41
end

if ((fis = fopen(nume_fli,"rb")) == NULL)


begin
fprintf(stderr,"Nu putem deschide acest fisier.\n");
return 1;
end

// Se iniţializează modul grafic


SetVideoMode(19);
fread(&size,4,1,fis);
fread(&magic,2,1,fis);
fread(&frames,2,1,fis);
fread(&width,2,1,fis);
fread(&height,2,1,fis);
fread(&aux,2,1,fis);
fread(&flags,2,1,fis);
fread(&speed,2,1,fis);
fread(&next,4,1,fis);
fread(&frit,4,1,fis);
fread(&expand,sizeof(&expand),1,fis);

for (int i=2; i<=frames; i++)


begin // header
delay(2*speed);
fread(&sizef,4,1,fis);
fread(&magicf,2,1,fis);
fread(&chuncks,2,1,fis);
fread(&expandf,sizeof(&expandf),1,fis);

for (int j=1; j<=chuncks; j++)


begin
fread(&sizec,4,1,fis); fread(&ty1,1,1,fis);
fread(&ty2,1,1,fis);
switch(ty1) begin
case FLI_COLOR: begin
fread(&numpackets,2,1,fis);
for (long int k=1;k<=numpackets; k++)
begin
fread(&colskip,1,1,fis);
fread(&colchange,1,1,fis);
if (colchange==0) then colc=256;
else colc=colchange;
for (int ii=0; ii<colc; ii++)
begin
fread(&red,1,1,fis);
fread(&green,1,1,fis);
fread(&blue,1,1,fis);
SetColorRegister(ii,red,green,blue);
end
end
break;
end
case FLI_LC:
begin
fread(&numlines,2,1,fis);
yy = numlines;
fread(&changelines,2,1,fis);
if (changelines > height) then
changelines /= 256;
for (int jj=0; jj<changelines; jj++)
begin // o linie compresată jj
fread(&numpack,1,1,fis); xx = 0;
if (numpack != 0) then

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

Suntem convinşi că un bun programator, cu o oarecare practică în domeniul graficii


computaţionale, va putea realiza cu uşurinţă un editor grafic de fişiere FLI, care să semene cu
Autodesk Animator sau 3D Studio pentru a-şi creea propriile fişiere FLI, după dorinţă, cu tehnicile
proprii de desenare. El va trebui să realizeze un editor grafic obişnuit, care să creeze cadrele flic-
ului. Apoi, urmînd schema de codificare a cadrelor, va trebui să salveze această succesiune de cadre
într-un fişier FLI. Mai întîi se vor salva datele importante despre fişier, în header. Apoi se va salva
paleta de culori folosită (FLI_COLOR), urmată de primul cadru desenat (FLI_BRUN). În
continuare, se vor determina, diferenţele dintre cadrul curent şi cel anterior, salvîndu-se sub forma
de cadru de tip FLI_LC. Fireşte, dacă diferenţele sunt prea mari (ca număr) de pixeli, se va face un
cadru de tip FLI_COPY. Dacă se schimbă paleta de la un cadru la altul, atunci un cadru intermediar
FLI_COLOR va fi inserat. Dacă ecranul se şterge cumva, la un cadru, se va pune un FLI_BLACK.

Mult succes !

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

3.1. Formatul fonturilor CHR


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

; offset 0h este un header specific firmei Borland:


HeaderSize de ex. 080h
DataSize de ex. (mărimea fişierului) ; lungimea fiş. CHR
descr de ex. "Triplex font" ; descriere
fname de ex. "TRIP" ; numele fişierului
MajorVersion de ex. 1 ; versiunea fontului
MinorVersion de ex. 0 ; subversiunea
db 'PK',8,8
db 'BGI ',descr,' V'
db MajorVersion+'0'
db (MinorVersion / 10)+'0',(MinorVersion mod 10)+'0'
db ' - 19 October 1987',0DH,0AH
db 'Copyright (c) 1987 Borland International', 0dh,0ah ; (c)
db 0,1ah ; null & ctrl-Z = end
dw HeaderSize ; mărimea header-ului
db fname ; numele fontului
dw DataSize ; mărimea fiş. font
db MajorVersion,MinorVersion ; numărul de versiune
db 1,0 ; nr.e de versiune min.
db (HeaderSize - $) DUP (0) ; tampon

La offset-ul 80h încep datele pentru fişier, după cum urmează:


; 80h '+' indicatoare pentru tipul fişierului stroke
; 81h-82h numărul de caractere în fişierul font (n)
; 83h nedefinit
; 84h valoarea ASCII a primului caracter din fişier

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>

Codurile de operaţii sunt:


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

Descrierea în limbajul C a structurii unui font CHR este dată mai jos:
/*
FONT.H - informaţiile din header-ul unui fişier de fonturi CHR
Copyright (c) 1988,1989 Borland International
*/
#define Prefix_Size 0x80
#define Major_Version 1
#define Minor_Version 0
#define SIGNATURE ‘+’

enum OP_CODES {
END_OF_CHAR = 0, DO_SCAN = 1, MOVE = 2, DRAW = 3
};

typedef struct {
char sig; /* SIGNATURE byte */
int nrchrs; /* numărul de caractere in fişier */
char mystery; /* nedefinit incă */
char first; /* primul caracter din fişier */
int cdefs; /* offset la definiţiile de caractere */
char scan_flag;/* True dacă setul de caractere este scanabil */
char org_to_cap;/*inălţimea de la origine la vf. literei mari */
char org_to_base;/* inălţimea de la origine la linia de bază */
char org_to_dec; /* inălţ. de la origine la linia inferioară */
char fntname[4]; /* patru caractere pentru numele fontului */
char unused; /* nedefinit incă */
} HEADER;

typedef struct {
char opcode; /* Byte pentru codul de operaţie */
int x; /* Offset relativ pe direcţia x */
int y; /* Offset relativ pe direcţia y */
} STROKE;

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;

Pornind de la aceste informaţii, se pot dezvolta proceduri de încărcare a fonturilor şi de


afişaj în orice direcţie.

3.2. Rutine speciale de afişare


/* Program pentru afişări speciale
(C) 1995 Bogdan Pătruţ */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <string.h>
#include "font.h"

FILE *ffile;

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 );

/* Această funcţie decodifică formatul fişierului,


punând-o într-o structură internă mai convenabilă */

int unpack( char *buf, int index, STROKE **neww )


{
unsigned int *pb;
STROKE *po;
int num_ops = 0;
int jx, jy, opcode, i, opc;
pb = (unsigned int *)(buf + index);

while( FOREVER ){
num_ops += 1;
opcode = decode( pb++, &jx, &jy );
if( opcode == END_OF_CHAR ) break;
}
po = (*neww = (STROKE*)calloc( num_ops, sizeof(STROKE) ));
if( !po ){
exit( 100 );
}
pb = (unsigned int *)(buf + index);

for( i=0 ; i<num_ops ; ++i ){


opc = decode(pb++, &po->x, &po->y);
po->opcode = opc;
po++;
}
return( num_ops );
}

/* Această funcţie decodifică un singur cuvânt din fişier punându-l într-o


structură de tip “stroke” */

int decode( unsigned int *iptr, int *x, int *y )


{
struct DECODE {
signed int xoff : 7;
unsigned int flag1 : 1;
signed int yoff : 7;
unsigned int flag2 : 1;
} cword;

cword = *(struct DECODE *)iptr;


*x = cword.xoff;
*y = cword.yoff;
return( (cword.flag1 << 1) + cword.flag2 );
}

void WriteChar(char litera, float alfa, int x0, int y0, int xl, int yl)
{
STROKE *sptr;
int j, i = litera;
int xx, yy, xxr, yyr;
int Caracter = unpack( Font, Offset[i], &sptr );
y0 = getmaxy() - y0; yl = getmaxy() - yl;
for( j=0 ; j<Caracter ; ++j, ++sptr ){

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;

for (int i=0; i<l; i++)


{
alfa = -2*M_PI*i/l;
xx = x0+raza * cos(alfa);
yy = getmaxy()-y0+raza * sin(alfa);
WriteChar(cuvint[i], M_PI/2-alfa, xx, yy, xx, yy);
}
}

void SinWrite(char * cuvint, int x, int y, int raza)


{
float alfa;
int l = strlen(cuvint);
for (int i=0; i<l; i++)
{
alfa = -4*M_PI*i/l;
y = y + raza * sin(alfa);
WriteChar(cuvint[i], 0, x, y, x, y);
x += Char_Width[cuvint[i]];
}
}

void main()
{
long length, current;
char *cptr;
STROKE *sptr;

ffile = fopen( "scri.chr", "rb" );


if( NULL == ffile ){
exit( 1 );
}
fread(Prefix, Prefix_Size, 1, ffile);
cptr = Prefix;
while( 0x1a != *cptr ) ++cptr;
*cptr = '\0';

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)

Credem că cititorul este conştient de importanţa pe care o au jocurile în educarea omului,


încă din anii copilăriei. Calculatoarele, prin posibilităţile lor de a face rapid o serie întreagă de
calcule şi de a reprezenta grafic diferite obiecte din natură, pot face aşa încât să simuleze pe
ecranele lor aproape orice joc care poate fi practicat şi altfel, de la jocurile de cărţi sau zaruri, până
la şah sau go, de la jocurile cu cuvinte, până chiar la jocurile sportive, deoarece pe calculator pot fi
simulate şi animate o serie întreagă de activităţi din lumea reală, din natură.
Putem spune, astfel, că jocurile pe calculator reprezintă în mic, pe ecran, lumea mare reală.

Ce implică un joc pe calculator ? Această problemă are două componente:


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

4.1. Ce presupune realizarea unui joc pe calculator


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

Referitor la a doua întrebare, existenţa unui joc pe calculator înseamnă existenţa unui nou
mod de recreere, care poate fi util în pauzele de serviciu ale persoanelor care utilizează calculatoare
personale în activitatea lor profesională. Dacă jocul este de logică, atunci el va ajuta la dezvoltarea

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.

De aceea ne pronunţăm pentru realizarea de jocuri pe calculator, ca un exerciţiu util şi


plăcut de programare, dar împotriva exagerării folosirii calculatoarelor pentru jocuri.

4.2. Ce jocuri putem realiza împreună, pe calculator ?1


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

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

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.

program BitMapToPascalImages; { BMP2IMA.PAS }


uses Crt,Graph,ViewBMP,uMouse;

procedure SalvImag(fisier: String; imag: Pointer; nrbytes: Word);


var imagefisier: File;
control: Integer;
begin
Assign(imagefisier,fisier);
ReWrite(imagefisier);
BlockWrite(imagefisier,imag^,(nrbytes Div 128)+1,control);
Close(imagefisier);
FreeMem(imag,nrbytes)
end;

procedure ApelImag(fisier:string;var imag:Pointer;nrbytes:word);


var imagefisier:File;
control:Integer;
begin
GetMem(imag,nrbytes);
Assign(imagefisier,fisier);
Reset(imagefisier);
BlockRead(imagefisier,imag^,(nrbytes Div 128)+1,control);
Close(imagefisier)
end;

var Size:Word;
P:Pointer;
nume_fis: String;
gd,gm,b,x1,y1,x2,y2: Integer;
begin
Write('Nume BMP : '); ReadLn(nume_fis);
gd:=Detect; InitGraph(gd,gm,'C:\TP\BGI');
LoadPackBMPFile(0,0,nume_fis+'.BMP');
MouseInit;
repeat MouseData(b,x1,y1) until b=1;
SetWriteMode(1); MouseHide;
{ zona de imagine care trebuie transformata se selecteaza
ca o zona dreptunghiulara, cu ajutorul mouse-ului }
repeat
MouseData(b,x2,y2);
Rectangle(x1,y1,x2,y2);
MouseShow; Delay(50); MouseHide;
Rectangle(x1,y1,x2,y2)
until b=0;
Size:=ImageSize(x1,y1,x2,y2);
GetMem(P,Size); GetImage(x1,y1,x2,y2,P^);
SalvImag(nume_fis+'.IMA',P,Size);
ClearViewPort; ApelImag(nume_fis+'.IMA',P,Size);
PutImage(0,0,P^,NormalPut);
FreeMem(P,Size); ReadLn;
CloseGraph;
end.

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;

procedure Micsoreaza(x1,y1,x2,y2: Integer; coefx,coefy: Byte);


{ se micşorează imaginea din zona dreptunghiulară dată de
colţul stânga-sus = (x1,y1) şi colţul dreapta-jos = (x2,y2);
coeficienţii de redimensionare sunt coefx (pe orizontală),
respectiv coef(y), pe verticală;
pentru o injumătăţire, pe ambele direcţii a imaginii,
se va apela procedura aceasta cu coeficienţii 2 si 2;
micşorarea imaginii poate duce la pierderea de informaţii utile
din cadrul imaginii; astfel, apelarea unei măriri după o
micşorare nu inseamnă neapărat restaurarea imaginii iniţiale !
de fapt, procedura Micsoreaza sare peste unii pixeli;
imaginea rezultată, va avea colţul stânga-sus tot în (x1,y1),
ca şi imaginea iniţială }
var c,x,y,i,j: Integer;
begin
i:=x1-1;
x:=x1;
while x<=x2 do
begin
Inc(i);
j:=y1-1;

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;

procedure Mareste(x1,y1,x2,y2: Integer; coefx,coefy: Byte);


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

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

pentru 16 culori:

program RedimensionareBMP16Culori;
uses Graph, ViewBMP;

procedure OpenGraph; { iniţializare mod grafic }


var gd,gm: Integer;
begin gd:=Detect; InitGraph(gd,gm,'C:\TP\BGI') end;

procedure ClearView(x1,y1,x2,y2: Integer); { şterge un ViewPort }


begin
SetViewPort(x1,y1,x2,y2, ClipOn);
ClearViewPort;
SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn)
end;

{$I RESIZE.INC}

begin
OpenGraph;
LoadPackBMPFile(0,0,'CARS.BMP');

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

... şi pentru 256 culori:

program RedimensionareBMP256Culori;

uses MCGA, ViewBMP;


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

Puteţi să utilizaţi procedurile de redimensionare a imaginilor, precum şi cele două proceduri


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

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

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

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ă.

5.1. Animaţie la “Turnurile din Hanoi”


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

program TurnurileDinHanoi;
uses Crt;

57
const Pauza=50; forma='Û'; { #78 }
var Virf: array [1..3 ] of Byte;

procedure HideCursor; assembler;


{ ascunde cursorul pâlpâitor, din modul text }
asm
MOV AX,$0100; MOV CX,$2607; INT $10
end;

procedure ShowCursor; assembler;


{ reafişează cursorul }
asm
MOV AX,$0100; MOV CX,$0506; INT $10
end;

function ColTija (tija : Byte) : Byte;


begin
ColTija := 24*tija-8
end;

procedure MutaDreapta (disc, tija1, tija2 : Byte);


var i,k: Byte;
begin
for i := ColTija(tija1)-disc to Pred(ColTija(tija2)-disc) do
begin
Delay(Pauza);
if KeyPressed then Halt(1);
GoToXY(i,3);
for k:=0 to 2*disc do
Write(' ');
GoToXY(i+1,3);
for k:=0 to 2*disc do
Write(forma)
end
end;

procedure MutaStinga (disc, tija1, tija2 : Byte);


var i,k: Byte;
begin
for i := ColTija(tija1)-disc
downto Succ(ColTija(tija2)-disc) do
begin
Delay(Pauza);
if KeyPressed then Halt(1);
GoToXY(i,3);
for k:=0 to 2*disc do
Write(' ');
GoToXY(i-1,3);
for k:=0 to 2*disc do
Write(forma)
end
end;

procedure Coboara (disc, tija : Byte);


var i,k: Byte;
begin
for i := 3 to Pred(Virf[tija]-1) do
begin
Delay(Pauza);
if KeyPressed then Halt(1);

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 Ridica (disc, tija : Byte);


var i,k: Byte;
begin
for i := Virf[tija] downto 4 do
begin
Delay(Pauza);
if KeyPressed then Halt(1);
GoToXY(ColTija(tija)-disc,i);
for k:=0 to 2*disc do
Write(' ');
GoToXY(ColTija(tija)-disc,i-1);
for k:=0 to 2*disc do
Write(forma)
end;
Inc(virf[tija])
end;

procedure Muta(disc, tija1, tija2 : Byte);


begin
Ridica(disc,tija1);
if (tija1 < tija2) then
MutaDreapta(disc,tija1,tija2)
else
MutaStinga(disc,tija1,tija2);
Coboara(disc,tija2)
end;

procedure Han(n, tija1, tija2, tija3 : Byte);


begin
if (n = 1) then
Muta(1,tija1,tija2)
else begin
Han(n-1,tija1,tija3,tija2);
Muta(n,tija1,tija2);
Han(n-1,tija3,tija2,tija1)
end
end;

procedure Initializari;
var k,disc: Byte;
begin
HideCursor;
Virf[1]:=13;
Virf[2]:=22;
Virf[3]:=22;
ClrScr;
for disc:=1 to 9 do
begin
GoToXY(ColTija(1)-disc,Virf[1]+disc-1);
for k:=0 to 2*disc do

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.

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

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

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!

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

program Bila;
uses Crt;
type pozitie = record
linie: Integer;
coloana: Integer
end ;

var ColBila,LinBila,ColNouaBila,
LinNouaBila,DeplColBila,DeplLinBila: Integer;
LinPal1,LinPal2,DeplLinPal1,DeplLinPal2,PctJuc1,PctJuc2: Integer;
Tasta: Char;
i: Integer;
Sonor, GataRepriza,GataJoc: Boolean;
urma: array [1..5] of pozitie;

61
procedure PrintAt(x,y: Integer; S: String);
begin
GoToXY(x,y); Write(S)
end;

procedure Beep(n,m: Integer);


begin
if Sonor then begin Sound(n); Delay(5*m); NoSound end
end;

procedure Chenar;
begin
ClrScr;
for i:=1 to 80 do
begin
PrintAt(i,1,'²');
PrintAt(i,24,'²')
end;
for i:=1 to 7 do
begin
PrintAt(1,i,'²');
PrintAt(1,25-i,'²');
PrintAt(80,i,'²');
PrintAt(80,25-i,'²')
end;
for i:=10 to 15 do
PrintAt(40,i,'²')
end;

procedure Mesaje;
var s: String;
begin
Write('Doriti sonor ? [DA/NU] > ');
ReadLn(s);
Sonor := not (UpCase(s[1])='')
end;

procedure Initializeaza;
begin
ColBila:=3+random(10);LinBila:=3+random(10);
for i:=1 to 5 do
begin
urma[i].coloana:=ColBila;
urma[i].linie:=LinBila
end;
DeplColBila := Random(3)-1; DeplLinBila := Random(3)-1;
if (DeplColBila*DeplLinBila=0) then
begin
DeplColBila := 1; { implicit dreapta - jos }
DeplLinBila := 1
end;
LinPal1:=12;LinPal2:=12;DeplLinPal1:=0;DeplLinPal2:=0;
for i:=-2 to 2 do
begin
PrintAt(2,LinPal1+i,'±');
PrintAt(79,LinPal2+i,'±')
end
end;

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}

{ nu trebuie depăşite marginile de către palete }


if LinPal1>21 then LinPal1:=21;
if LinPal1<4 then LinPal1:=4;
if LinPal2>21 then LinPal2:=21;
if LinPal2<4 then LinPal2:=4;

{ redesenare a paletelor în noua poziţie }


for i:=-2 to 2 do
begin
PrintAt(2,LinPal1+i,'±');
PrintAt(79,LinPal2+i,'±')
end
end
end;

procedure MutaBila;
begin
MutaPaletele;

{ lovire margini de sus sau de jos }


if (LinBila = 23) or (LinBila = 2) then
begin
DeplLinBila := -DeplLinBila;
{ schimbare sens de deplasare }
Beep(100,10)
end;

{ lovire margini din stânga sau din dreapta }

{ bila este in apropierea zidului din stânga ... }


if ColBila = 3 then
{ se testează dacă paleta 1 nu e în apropiere }
if abs(LinPal1-LinBila) <= 2 then
begin
{ dacă bila este lovită de paleta 1, atunci aceasta îi poate
schimba viteza, însă viteza se incadrează în limita -2 .. +2 }
if Tasta = 'q' then DeplLinBila := DeplLinBila - 1;
if Tasta = 'a' then DeplLinBila := DeplLinBila + 1;
if DeplLinBila <- 2 then DeplLinBila := -2;
if DeplLinBila >+ 2 then DeplLinBila := +2;
DeplColBila := -DeplColBila;

63
{ schimbare sens de deplasare }
Beep(450,10)
end;

if ColBila = 2 then { s-a marcat în poarta 1 ? }


if ((LinBila >= 2) and (LinBila <= 7)) or
((LinBila <= 23) and (LinBila >= 18))
then
{ nu s-a marcat, ci s-a atins zidul, deci se schimbă sensul }
DeplColBila := -DeplColBila
else begin { s-a marcat in poarta juc.1,
deci jucătorul 2 primeşte puncte }
for i := 1 to 10 do Beep(500-25*i,10);
Inc(PctJuc2);
GataRepriza := True { s-a terminat incă o repriză }
end;

{ condiţii şi acţiuni analoage şi în cazul zidului din dreapta }


if ColBila = 78 then
if abs(LinPal2-LinBila) <= 2 then
begin
if Tasta = 'p' then DeplLinBila := DeplLinBila - 1;
if Tasta = 'l' then DeplLinBila := DeplLinBila + 1;
if DeplLinBila <- 2 then DeplLinBila := -2;
if DeplLinBila > +2 then DeplLinBila := +2;
DeplColBila := -DeplColBila;
Beep(450,10)
end;

if ColBila = 79 then
if ((LinBila >= 2) and (LinBila <= 7)) or
((LinBila <= 23) and (LinBila >= 18))
then DeplColBila := -DeplColBila
else begin
for i := 1 to 10 do Beep(500-25*i,10);
Inc(PctJuc1);
GataRepriza := True
end;

{ lovire centru ... }


if ((LinBila > 9) and (LinBila < 16)) and
((ColBila = 39) or (ColBila = 41)) then
begin
DeplColBila := -DeplColBila;
Beep(600,10)
end;
ColNouaBila := ColBila + DeplColBila;
if ColNouaBila < 2 then ColNouaBila := 2;
if ColNouaBila > 79 then ColNouaBila := 79;

LinNouaBila := LinBila + DeplLinBila;


if LinNouaBila < 2 then LinNouaBila := 2;
if LinNouaBila > 23 then LinNouaBila := 23;

{ ştergem bila din poziţia veche }


PrintAt(ColBila,LinBila,' ');
{ actualizăm urma şi o afişăm... }
for i := 1 to 4 do
begin
urma[i] := urma[i+1];

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;

{ bila se afişează la noile coordonate }


PrintAt(ColNouaBila,LinNouaBila,'o');
ColBila := ColNouaBila;
LinBila := LinNouaBila;
Delay(45);
MutaPaletele;
end;

begin {** PROGRAM **}


ClrScr;
{ mărire viteză cursor }
Port[$60] := $F3; Delay(200); Port[$60] := 0;
PctJuc1 := 0; PctJuc2 := 0;
mesaje;
GataJoc := False;
repeat
GataRepriza := false;
Chenar;
Initializeaza;
MutaPaletele;
repeat
MutaPaletele;
MutaBila;
MutaPaletele;
if GataRepriza then
begin
GoToXY(30,24);
Write(' SCOR : ',PctJuc1:2,
' -- ',PctJuc2:2,' ');
Delay(1500);
Chenar
end;
if (PctJuc1=5) or (PctJuc2=5) then
GataJoc:=true
until GataRepriza
until GataJoc;
PrintAt(32,7,'* JOC TERMINAT *');
GoToXY(32,8); Write('SCOR : ',PctJuc1:2,' -- ',PctJuc2:2);
GoToXY(20,22);
if PctJuc1<PctJuc2 then
Write('JUCĂTORUL DIN DREAPTA A CÂŞTIGAT .')
else
Write('JUCĂTORUL DIN STÂNGA A CÂŞTIGAT .');
GoToXY(1,1);
repeat until KeyPressed;
ClrScr
end.

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

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

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;

procedure HideCursor; assembler; { se foloseşte întreruperea 10h }


asm
MOV AX,$0100; MOV CX,$2607; INT $10
end;

procedure ShowCursor; assembler;


asm
MOV AX,$0100; MOV CX,$0506; INT $10
end;

procedure PrintAt(x, y: byte; S: string; f, b: byte);


type VideoLocation = record
{formatul unei locaţii video pe doi bytes }
VideoData: Char; { caracterul afişat }
VideoAttribute: Byte;
{ atributele (culorile) }
end;
var i: Byte;
VideoSegment: Word; { locaţia memoriei video }
MonoSystem: Boolean; { mono vs. color }
VidPtr: ^VideoLocation; { pointer la locaţii video }
begin
{ Determină locaţia de memorie unde va fi tipărit şirul,
în concordanţă cu tipul monitorului şi cu locaţia de pe ecran
(coordonatele x si y).
Apoi asociază pointerul VidPtr cu acea locaţie: VidPtr este un
pointer către tipul VideoLocation. Inserează un caracter şi
atributele sale, după care trece la noul caracter şi la noua

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.

Orientat obiect, programul următor, este o modificare a programului Invadatorii, pe care l-


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

program Navele;
uses Crt, MyCrt;

const kbUp=#72; kbDown=#80; kbLeft=#75; kbRight=#77;


kbSpace=#32; kbAltS=#31; kbEsc=#27; kbF1=#59;
Pauza1=50; Pauza2=10;

type Str3 = String[3];


TNava = object
x,y,dx,dy,cul: Byte;
model: Str3;
constructor Init(x0,y0: Byte; model0: Str3);
procedure Display;
procedure Clear;
procedure Move;
destructor Done;
end;

var Nava: array[1..20] of TNava;


NrNave: Integer;

type TOm = object


c,nv:integer; { nv = nr. de vieţi }
constructor Init(c0:integer);
procedure Move;
destructor Done;
end;
var Om:TOm;

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

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;

procedure Beeps; { sunete de mare efect se pot obţine, dacă se înlocuieşte


acel 400 cu o valoare mai mare, de exemplu 1000 }
var i: Integer;
begin
for i:=300 to 400 do begin
Tone(10*i);
Delay(5) end;
NoTone
end;

constructor TNava.Init;
begin
x:=x0; y:=y0; model:=model0;
dx:=Random(3)-1; dy:=Random(3)-1; cul:=9+Random(7)
end;

procedure TNava.Display;
begin PrintAt(x-1,y,model,cul,Black) end;

procedure TNava.Clear;
begin PrintAt(x-1,y,' ',Black,Black) end;

procedure TNava.Move;
var i: Integer;
begin
Clear; Inc(x,dx); Inc(y,dy);
if x<2 then x:=2; if x>78 then x:=78;
if y<2 then y:=2; if y>20 then y:=20;
Display; Delay(Pauza2); dx:=Random(3)-1; dy:=Random(3)-1
end;

destructor TNava.Done;
begin Clear end;

constructor TOm.Init(c0:integer);
begin
c:=c0; PrintAt(c,21,'=³=',Yellow,Black)
end;

procedure TOm.Move;
var comanda:char;
begin
if KeyPressed then begin
Dec(Timp); comanda:=ReadKey;
if comanda=kbEsc then Escape:=True
else begin
if comanda=kbSpace then
if not ExistaGlont then begin

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

Am implementat un simplu joc de Tetris în unit-ul uTet, în care JocDeTetris reprezintă


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

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

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

74
Moment din desfăşurarea unui joc de TETRIS

Procedura JocDeTetris spuneam, se află în unit-ul uTet, unit pe care îl prezentăm în


continuare, plin de comentarii. Dacă nu înţelegeţi ceva, atunci porniţi jocul şi rulaţi-l pas cu pas,
sărind peste procedurile înţelese la un moment dat şi tot aşa, până înţelegeţi totul.

unit utet;

interface
uses Graph,Crt;
procedure JocDeTetris(est,nord,NivelJoc: Integer;
var scor: Integer);

implementation

const kbUp=#72; kbDwn=#80; kbLft=#75; kbRgt=#77;


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

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;

procedure DeseneazaPatrat(x,y,c: Integer);


{ desenează una din componentele piesei, în culoarea c;
dacă c este Black, înseamnă că, de fapt, se şterge pătratul }
begin
SetColor(c);
SetFillStyle(SolidFill,c);
Bar(x00+10*x,10*y+y00,x00+10*x+10-1,10*y+10-1+y00);
if c<>Black then
begin
SetColor(DarkGray);
Rectangle(x00+10*x+1,y00+10*y+1,
x00+10*x+10-2,y00+10*y+10-2)
end
end;

procedure DeseneazaPiesa(c: Integer);


{ desenează piesa curentă, în culoarea c, care va fi fie
CuloarePiesa, fie Black, caz în care de fapt se şterge piesa,
pentru a fi reafişată în noua poziţie/ipostază }
begin
DeseneazaPatrat(x1,y1,c); DeseneazaPatrat(x2,y2,c);
DeseneazaPatrat(x3,y3,c); DeseneazaPatrat(x4,y4,c)
end;

function SePoateMuta: Boolean;


{ testează dacă este posibilă o deplasare, după comanda
sau conform coborârii obişnuite, a piesei curente }
var SePoate: Boolean;
begin
SePoate:=true;
if (ax1<0) or (ax2<0) or (ax3<0) or (ax4<0)
or (ax1>9) or(ax2>9) or (ax3>9) or (ax4>9) then
SePoate := False { depăşire limite cutie }
else
if (Cutie[ay1,ax1]=plin) or (Cutie[ay2,ax2]=plin) or
(Cutie[ay3,ax3]=plin) or (Cutie[ay4,ax4]=plin) then

76
SePoate := False;
{ într-una din noile coordonate cutia ar fi umplută }
SePoateMuta:=SePoate
end;

function EstePlina(linia: Integer): Boolean;


{ testează dacă linia este plină cu alte piese, pentru a o
elimina din cutie, a mări punctajul
şi a actualiza liniile cutiei }
var EPlina: Boolean;
i: Byte;
begin
EPlina := True;
i := 0;
while (i<10) and EPlina do
{ se caută primul gol din linie, dacă acesta există }
if Cutie[linia,i] = gol then
EPlina:=false
else Inc(i);
EstePlina := EPlina
end;

function NuMaiEsteLoc: Boolean;


begin
NuMaiEsteLoc := (Cutie[y1,x1]=plin) or
(Cutie[y2,x2]=plin) or
(Cutie[y3,x3]=plin) or
(Cutie[y4,x4]=plin)
end;

procedure ActualizeazaPozitiePiesa;
{ mută piesa curentă: o şterge din poziţia veche,
îi determină poziţia nouă şi o desenează acolo }
begin
DeseneazaPiesa(Black);
x1:=ax1; x2:=ax2; x3:=ax3; x4:=ax4;
y1:=ay1; y2:=ay2; y3:=ay3; y4:=ay4;
DeseneazaPiesa(CuloarePiesa)
end;

procedure MutaStinga;
begin
ax1:=x1-1; ax2:=x2-1; ax3:=x3-1; ax4:=x4-1;
ay1:=y1; ay2:=y2; ay3:=y3; ay4:=y4;
if SePoateMuta then ActualizeazaPozitiePiesa
end;

procedure MutaDreapta;
begin
ax1:=x1+1; ax2:=x2+1; ax3:=x3+1; ax4:=x4+1;
ay1:=y1; ay2:=y2; ay3:=y3; ay4:=y4;
if SePoateMuta then ActualizeazaPozitiePiesa
end;

procedure Coboara;
begin
ax1:=x1; ax2:=x2; ax3:=x3; ax4:=x4;
ay1:=y1+1; ay2:=y2+1; ay3:=y3+1; ay4:=y4+1;
if SePoateMuta then ActualizeazaPozitiePiesa

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 JocDeTetris(est,nord,NivelJoc: Integer;


var scor: Integer);
{ ... joc de Tetris ...}

procedure DeseneazaCutia;
begin
{ desenez cutia }
SetColor(White);
Rectangle(x00-15,y00-15,x00+117,y00+245);
SetFillStyle(SolidFill,Cyan);
Bar(est-10,nord,est-2,nord+240);
Bar(est+102,nord,est+112,nord+240);
Bar(est,nord+232,est+100,nord+240);
OutTextXY(est+20,nord+233,' TETRIS ')
end;

procedure InitializeazaCutia;
begin
{ iniţializare cutie: la început ea e goală ... }
for i:=0 to 22 do
for j:=0 to 9 do
Cutie[i,j] := gol;
{ consider baza cutiei, exterioară, dar plină, pentru
a verifica mai uşor când vine o piesă şi trebuie să
se oprească jos ... }
for i:=0 to 9 do
Cutie[23,i] := plin
end;

procedure DisplayScor;
var SirScor: String[5];
begin
Str(scor,SirScor);
SetColor(GetBkColor);
OutTextXY(est+3,nord-10,'Puncte:ÛÛÛÛÛÛÛ');
SetColor(White);
OutTextXY(est+3,nord-10,'Puncte:'+sirscor);
end;

procedure ActualizeazaScor;
begin
{ scorul depinde de piesa aleasă;
el este mai mic pentru piesele simple:
bara şi pătratul }
if Piesa in [1,2,5,6] then
scor:=scor+2*(70-NivelJoc)
else scor:=scor+70-NivelJoc;

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.

Aspect din jocul FEŢE

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:

lat = 88 = lăţimea unei piese, deci şi a unei imagini întregi;


inalt = 38 = înălţimea unei piese, adică a treia parte
din înălţimea unei imagini (114);
SizeOfImage = 22182 = mărimea unei imagini (dată de ImageSize)

Puteţi să modificaţi şi numărul de turnuri (NrTurnuri) pe care se pot aşeza piesele în


cădere, precum şi numărul de linii ale unui turn (NrLinii). Dacă dispuneţi de mai multe sau mai
puţine imagini, va trebui să modificaţi constanta corespunzătoare (NrFete).

Iată textul sursă al programului:

program FeteDeOameni;
uses Crt, Graph, Graph1{, ViewBMP};
label sfirsit;
const lat=88; inalt=38;
NrTurnuri=4; NrFete=4; NrLinii=12;
SizeOfImage=22182;

type pereche=record unu,doi: Byte end;


var top: array[1..NrTurnuri] of Byte;
turn: array[1..NrTurnuri,1..NrLinii] of pereche;
ii: Byte; Size: Word; pauza, cod: Integer;
raspuns: String; premiu: Integer; sir: String;
dorinta: Boolean;
Xmij, Ymij: Integer;

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 }

procedure ClearView(x1,y1,x2,y2: Integer);


begin
SetViewPort(x1,y1,x2,y2,ClipOn); ClearViewPort;
SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn)
end;

procedure Message(mesaj: String);


var Size: Word; l,b,x,y: Integer;
P: Pointer;
begin

{ afisez un mesaj de atentionare


si astept apasare buton mouse }
l:=TextWidth(mesaj) div 2 + 5;
Size:=ImageSize(Xmij-l,Ymij-10,Xmij+l,Ymij+10);
GetMem(P,Size);
GetImage(Xmij-l,Ymij-10,Xmij+l,Ymij+10,P^);
ClearView(Xmij-l,Ymij-10,Xmij+l,Ymij+10);
Rectangle(Xmij-l,Ymij-10,Xmij+l,Ymij+10);
SetTextJustify(CenterText,CenterText);
OutTextXY(Xmij,Ymij,mesaj);
SetTextJustify(LeftText,TopText);
Tasta := ReadKey;
Delay(300);
PutImage(Xmij-l,Ymij-10,P^,NormalPut);
FreeMem(P,Size)
end;

procedure GetName(mesaj: String; var nume: String);


var Size: Word; l,b,x,y: Integer;
P: Pointer; tasta: Char;
begin
{ afişez mesajul, apoi }
{ citesc de la tastatură nume }
l:=TextWidth(' ')
div 2 + 5; { caracterul cu codul 219 }
Size:=ImageSize(Xmij-l,Ymij-10,Xmij+l,Ymij+14);
GetMem(P,Size);
GetImage(Xmij-l,Ymij-10,Xmij+l,Ymij+14,P^);
ClearView(Xmij-l,Ymij-10,Xmij+l,Ymij+14);
Rectangle(Xmij-l,Ymij-10,Xmij+l,Ymij+14);
SetTextJustify(CenterText,CenterText);
OutTextXY(Xmij-8,Ymij,mesaj);
nume:='';
repeat
tasta:=ReadKey;
if tasta in ['0'..'9','a'..'z','A'..'Z',' ','-'] then
begin
nume:=nume+tasta;
SetColor(GetBkColor);
OutTextXY(Xmij,Ymij+8, { caracterul #219 }

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;

function StopJoc: Boolean;


var SJ: Boolean;
i,j: Byte;
begin
SJ:=False;
i:=1;
while (not SJ) and (i<=NrTurnuri) do
begin
if top[i]>=3 then { 3 - nr. de fragmente }
begin
if (turn[i,top[i]].doi=1) and
(turn[i,top[i]-1].doi=2) and
(turn[i,top[i]-2].doi=3) and
(turn[i,top[i]].unu=turn[i,top[i]-1].unu)
and
(turn[i,top[i]].unu=turn[i,top[i]-2].unu)
then
SJ:=True
end;
i:=i+1
end;
ii:=i-1;
if not SJ then ii:=0;
StopJoc:=SJ
end;

function Umplere: Boolean;


var i: Byte; gasit: Boolean;
begin
i:=1; gasit:=False;
while (i<=NrTurnuri) and (not gasit) do
if NrLinii-1 = top[i] then
gasit:=True
else Inc(i);
Umplere:=gasit
end;

procedure InitTabla;
var i,j: Byte;
begin

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 Scrie(i,j: Byte; t: pereche);


begin
PutImage((lat + lat div 2)*(i-1),GetMaxY-inalt*j,
Fata[t.unu,t.doi]^,CopyPut)
end;

procedure Spatiu(i,j: Byte);


begin
ClearView((lat + lat div 2)*(i-1),GetMaxY-inalt*j,
(lat + lat div 2)*i-5,GetMaxY-inalt*(j-1));
turn[i,j].unu:=0;
turn[i,j].doi:=0
end;

procedure DesTabla;
var i,j: Byte;
begin
for i:=1 to NrTurnuri do
for j:=1 to top[i] do
Scrie(i,j,turn[i,j])
end;

procedure LoadImage(x,y: Integer; fis:String);


var P:pointer;
f:file;
control:Integer;
begin
{incarc imagine .IMA}
fis:=fis+'.IMA';
GetMem(P,SizeOfImage);
Assign(f,fis);
{$I-}Reset(f);{$I+}
if IOResult = 0 then begin
BlockRead(f,P^,(SizeOfImage Div 128)+1,control);
Close(f);
PutImage(x,y,P^,NormalPut);
FreeMem(P,SizeOfImage)
end
else Message('Imagine negasită...')
end;

procedure PreiaImagine(x1,y1,x2,y2: Integer; var Imagine: Pointer);


begin
Size:=ImageSize(x1,y1,x2,y2);
GetMem(Imagine,Size);
GetImage(x1,y1,x2,y2,Imagine^)
end;

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;

begin { Program principal ... }


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

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.

7.3. Transformări de imagini


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

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 }

procedure CreatePic(var D: desen);


{ crează interactiv un desen D, folosind mouse-ul }

procedure DrawPic(D: desen; stil: YesNo);


{ afişează un desen D, în stilul stil }

procedure TransfPic(D1, D2: desen; n: Byte; stil: YesNo);


{ transformă, prin translaţie de puncte, desenul D1
în desenul D2;
transformarea este realizată în n paşi,
iar desenările se fac în stilul precizat }
procedure ModifyPic(var D: desen);

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

uses Graph, uMouse, Crt;


var pag: Byte; { numărul paginii curente ce se vizualizează }

procedure Beep; { avertizare sonoră }


begin Sound(200); Delay(100); NoSound end;

function Apartine(x,y,x1,y1,x2,y2: Integer): Boolean;


{ verifică apartenenţa unui punct (x,y)
la un dreptunghi (x1,y1)-(x2,y2) }
begin
Apartine := (x1<=x) and (x<=x2) and (y1<=y) and (y<=y2)
end;

procedure OpenGraph;
{ initializare grafică }
var gd,gm: Integer;
begin
gd:=VGA; gm:=VGAmed;
InitGraph(gd,gm,'C:\TP\BGI');
{ calea grafică către fişierele BGI }
pag:=0;
end;

procedure Bara;
{ şterge o porţiune din partea stângă sus a ecranului,
loc unde se fac unele citiri în cadrul procedurilor de
creare şi modificare de desen }
begin
SetViewPort(0,0,150,60,ClipOn); ClearViewPort;
SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn)
end;

procedure DrawSurface(S: surface; stil: YesNo);


{ desenează o suprafaţă S, în stilul stil }
var cc: Byte;
begin
cc:=GetColor; { culoarea dinaintea apelului procedurii }
case stil of
yes: begin
{ dacă este stilul "yes", atunci se desenează şi
linia poligonală }
SetColor(S.Cul);
DrawPoly(S.NrPct, S.Pct);
{ dar se umple şi poligonul }
SetFillStyle(S.Fil, S.Cul);
FillPoly(S.NrPct, S.Pct)
end;
no: begin { desenare simplă cu alb }
SetColor(White);

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;

procedure CreateSurface(var S: surface);


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

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;

procedure ModifySurface(var S: surface);


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

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;

procedure CreatePic(var D: desen);


{ creează un desen D, creând pe rând,
toate suprafeţele (liniile poligonale/curbele) ce îl compun }
var i: Integer;
begin
MouseHide;
Bara; {la început se precizează câte suprafeţe are desenul}
OutTextXY(0,0,'Câte suprafeţe ?');
GoToXY(1,2); ReadLn(D.NrSurf);
ClearDevice;
MouseShow;
for i:=1 to D.NrSurf do
CreateSurface(D.Surf[i])

95
end;

procedure DrawPic(D: desen; stil: YesNo);


{ afişează un desen D, desenând toate suprafeţele ce îl compun,
în acelaşi stil }
var i: Byte;
begin
for i:=1 to D.NrSurf do
DrawSurface(D.Surf[i], stil)
end;

procedure TransfPic(D1, D2: desen; n: Byte; stil: YesNo);


{ transformă un desen D1 într-un altul D2, în n paşi;
numărul de suprafeţe ale lui D2 este acelaşi ca şi a lui D1;
numărul de puncte ale fiecarei suprafeţe a lui D2,
ca şi parametrii Fil şi Cul sunt aceleaşi ca şi la D1 }

var S1,S2,S: surface;


k,i,j: Integer;
begin
MouseHide;
for i:=1 to n do
{ au loc n transformări, bazate pe translaţii }
begin
for k:=1 to D1.NrSurf do
{ se transformă, la pasul i,
toate cele D1.NrSurf suprafeţe,
după acelaşi model ca şi în
procedura TransfSurface }
begin
S1:=D1.Surf[k];
S2:=D2.Surf[k];
S.NrPct:=S1.NrPct;
S.Fil:=S2.Fil; S.Cul:=S2.Cul;
for j:=1 to S1.NrPct do
begin
S.Pct[j,1]:=Round( S1.Pct[j,1]+
i*(S2.Pct[j,1]-
S1.Pct[j,1])/n );
S.Pct[j,2]:=Round( S1.Pct[j,2]+

i*(S2.Pct[j,2]-
S1.Pct[j,2])/n )
end;
DrawSurface(S,stil);
end;
{ aici se realizează ştergerea paginii grafice
care nu se vede }
SetVisualPage(1-pag);
SetActivePage(pag);
ClearDevice;
Delay(PauzaDeAnimatie); { o mică pauză }
pag:=1-pag { inversare de pagini grafice }
end;
MouseShow
end;

procedure ModifyPic(var D: desen);


{ modificarea ununi desen D înseamnă modificarea
tuturor celor D.NrSurf linii poligonale ce îl compun }

96
var i: Byte;
begin
for i:=1 to D.NrSurf do
ModifySurface(D.Surf[i])
end;

procedure SavePic(D: desen; fis: String);


{ salvarea datelor unui desen D într-un fişier fis;
este procesul invers lui LoadPic,
pe care îl prezentăm mai jos }
var i,j: Byte; F: Text;
begin
Assign(F,fis); Rewrite(F);
WriteLn(F,D.NrSurf);
for i:=1 to D.NrSurf do
with D.Surf[i] do
begin
WriteLn(F,Fil); WriteLn(F,Cul);
WriteLn(F,NrPct);
for j:=1 to NrPct do
begin
WriteLn(F,Pct[j,1]);
WriteLn(F,Pct[j,2])
end
end;
Close(F)
end;

procedure LoadPic(var D: desen; fis: String);


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

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

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.

- pentru fişierul omhom1.pic:


6, 1, 14, 18, 201, 81, 201, 81, 165, 104, 164, 134, 178, 169, 205, 204, 239, 225, 269, 230, 300, 231, 321,
224, 340, 205, 360, 165, 367, 137, 347, 105, 309, 75, 269, 67, 229, 70, 205, 80, 1, 1, 8, 213, 110, 213,
110, 202, 115, 209, 122, 225, 123, 239, 119, 231, 111, 215, 110, 1, 1, 8, 299, 109, 299, 109, 284, 114,
297, 122, 317, 122, 327, 114, 314, 108, 301, 108, 1, 6, 20, 163, 101, 163, 101, 121, 34, 161, 49, 168, 20,
201, 44, 227, 9, 250, 42, 277, 4, 298, 43, 341, 17, 328, 53, 370, 33, 349, 103, 311, 74, 272, 66, 229, 69,
199, 81, 165, 101, 165, 101, 1, 3, 6, 256, 137, 256, 137, 252, 178, 279, 178, 263, 136, 257, 136, 1, 4, 10,
235, 199, 235, 199, 243, 206, 259, 210, 281, 209, 292, 203, 297, 197, 269, 201, 253, 201, 237, 198 .

- pentru fişierul omhom2.pic:


6, 1, 14, 18, 215, 91, 201, 92, 155, 92, 155, 132, 155, 201, 155, 241, 237, 241, 266, 241, 309, 241, 346,
241, 374, 241, 371, 165, 367, 137, 365, 91, 309, 91, 269, 91, 231, 90, 215, 91, 1, 1, 8, 217, 107, 207,
107, 195, 107, 196, 132, 243, 132, 243, 107, 228, 107, 217, 107, 1, 1, 8, 295, 107, 282, 108, 284, 114,
284, 131, 328, 131, 329, 115, 329, 107, 297, 107, 1, 6, 20, 151, 91, 63, 91, 130, 38, 151, 23, 165, 12,
201, 12, 226, 12, 259, 12, 278, 12, 319, 12, 340, 12, 358, 23, 454, 90, 365, 90, 304, 90, 268, 90,1 229, 90,
197, 91, 152, 91, 157, 89, 1, 3, 6, 253, 136, 252, 137, 254, 230, 284, 230, 284, 137, 255, 137, 1, 4, 10,
251, 232, 236, 232, 213, 232, 206, 253, 331, 253, 324, 242, 315, 232, 283, 232, 269, 232, 253, 232 .

După ce aţi creat, cu mare grijă, fişierele cuprinzând numerele de mai înainte şi aţi compilat
(cu tpc) programul Morphing, încercaţi o rulare cu comanda:

C : \ > morphing omhom1 omhom2

şi vedeţi ce se întâmplă.
Iată un moment din timpul rulării acestui program, moment în care avem pe ecran un desen
intermediar ce nu reprezintă nici un om, dar nici o casă.

Un desen intermediar din timpul unui morphing

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

x' = x1 + i * (x2 - x1) şi y' = y1 + i * (y2 - y1),

în care (x2,y2) reprezintă coordonatele punctului în care trebuie să ajungă punctul P în cei n
paşi, adică coordonatele acestui punct în cadrul liniei poligonale S2.
Iată, deci, procedura:

procedure TransfSurface(S1, S2: surface; n: Byte);


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

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

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.

Textul programului jocului Amestec este listat mai jos:

101
program Amestec;
uses Crt, MCGA, ViewBMP, uMouse;
const MaxNrBuc=20;
latime=250; inaltime=150;
autor: string = 'un joc de BOGDAN PATRUT';

type pereche=record X,Y: ShortInt end;

var NrBuc,i,j, imagine: Byte;


b,lat, inalt, xx,yy: Integer;
zona: array[0..MaxNrBuc,0..MaxNrBuc] of pereche;
sir: String; tasta:Char;
Escape: Boolean;

procedure Amestecare;
var b,x,y,k: Integer;
c: Char;
begin
repeat
xx:=Random(NrBuc+1); yy:=Random(NrBuc+1);
k:=Random(2);
if k=0 then
begin
MouseHide;
ReverseX(lat*xx,inalt*yy,lat*(xx+1)-1,inalt*(yy+1)-1);
zona[xx,yy].X:=-zona[xx,yy].X; MouseShow
end
else
begin
MouseHide;
ReverseY(lat*xx,inalt*yy,
lat*(xx+1)-1,inalt*(yy+1)-1);
zona[xx,yy].Y:=-zona[xx,yy].Y; MouseShow
end;
MCGAMouseData(b,x,y);
Delay(30)
until (b<>0)
end;

function StopJoc: Boolean;


var i,j: Integer;
SJ: Boolean;
begin
SJ:=True;
i:=0;
while (i<NrBuc) and SJ do
begin
j:=0;
while (j<NrBuc) and SJ do
if zona[i,j].X+zona[i,j].Y<>2 then SJ:=False
else j:=j+1;
i:=i+1
end;
StopJoc:=SJ
end;

procedure Refacere;
var b,x,y: Integer;
begin

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.

Aspect din jocul AMESTEC

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

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.

Programul se prezintă de la sine şi textul său este următorul:

program Spinzuratoarea;
uses MCGA, Crt, ViewBMP;
const NrMaxPasi=6;
dictionar: array[1..10] of String =
('SPINZURATOARE','SCIRTIALA','INFORMATICA','CATASTROFA','GURALIV',
'POVIRNIS','PISCATURA','ALBITURA','INCONDEIAT','POPOSIT');
var cuvint,cuv,nume_fis: String;
raspuns, lit1, lit2, lit: Char;
i,lung,ghicite, din_start, pas: Byte;
gasit, prima_data: Boolean;

procedure CRectangle(x1,y1,x2,y2,c: Integer);


begin SetColor(c); Rectangle(x1,y1,x2,y2) end;

procedure Deseneaza;
begin
case pas of
1: begin
CLine(10,180,100,180,12);
CRectangle(80,170,120,180,12)
end;
2: begin

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 AfisImagine(k,n: Byte);


begin
if prima_data then
begin
ClearView(160,0,319,199);
PLoadUnpackBMPFile(150,0,Round(200*k/n),nume_fis);
prima_data:=False
end
else
SPloadUnPackBMPFile(150,0,Round(200*k/n),nume_fis)
end;

procedure AfiseazaCuvintul;
var i: Byte;
begin PrintAt(1,8); PrintS(cuv) end;

procedure Prezentare;
begin LoadUnPackBMPFile(160,0,'Spinz.BMP') end;

begin
repeat
OpenGraph;
PrintAt(21,10); PrintS('Asteptati !');
Prezentare; SetColor(40);
PrintAt(1,2); PrintS(' SPINZURATOAREA');
SetColor(20); PrintAt(1,3);
PrintS(' ---------------'); SetColor(15);
Randomize; i:=Random(6)+1; Str(i:1,nume_fis);
nume_fis:='GIRL'+nume_fis+'.BMP'; prima_data:=True;
cuvint:=dictionar[Random(10)+1];
lung:=Length(cuvint); lit1:=cuvint[1]; lit2:=cuvint[lung];
PrintAt(1,8); PrintS(lit1);
cuv[0]:=chr(lung); cuv[1]:=lit1; cuv[lung]:=lit2; ghicite:=2;
for i:=2 to lung-1 do
if cuvint[i] in [lit1, lit2] then
begin
PrintS(cuvint[i]); cuv[i]:=cuvint[i]; Inc(ghicite)
end

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.

Imagine din timpul unui joc SPÂNZURĂTOAREA

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

uses uMouse, Crt;

function Min(x,y:integer):integer;
begin if x<y then Min:=x else Min:=y end;

function Max(x,y:integer):integer;
begin if x>y then Max:=x else Max:=y end;

procedure MCGAMouseData(var b,x,y:integer);


[ citire coordonate şi buton mouse, în modul grafic 320 x 200 ]
var x1:integer;
begin
MouseData(b,x1,y); x:=x1 div 2
end;

var _color_:byte; [ culoarea curenă ]


_bk_color_:byte; [ culoarea fondului ]
_x_poz_, _y_poz_, [ poziţia curentă a cursorului ],
_x_max_, _y_max_: Integer; [colţul dreapta-jos ]

procedure SetVideoMode; assembler;


[ setează modul de scriere ]
asm MOV AL, VideoCode; XOR AH,AH; INT 10h end;

procedure SetColorRegister; assembler;


[ setează un registru de culoare 0 =< RegValue <= 255 ]
asm MOV AX,1010H; MOV BX,RegColor; MOV CH,GreenValue;
MOV CL,BlueValue; MOV DH,RedValue; INT 10h
end;

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 PutPixel(X, Y: Integer; Pixel: Byte);


begin
if WriteMode = 1 then [ pentru modul de scriere Xor ]
Pixel := Pixel XOR GetPixel(X,Y);
asm MOV AL,Pixel; MOV AH,0CH; MOV CX,X; MOV DX,Y; INT 10h end
end;

function GetPixel(X, Y: Integer): Byte; assembler;


asm MOV AH,0DH; MOV CX,X; MOV DX,Y; INT 10H end;

procedure OpenGraph;
[ iniţializare grafică ]
begin
SetVideoMode($13);
_x_max_:=319; _y_max_:=219;
SetColor(15); WriteMode:=0
end;

function GetMaxX: Integer;


begin GetMaxX := _x_max_ end;

function GetMaxY: Integer;


begin GetMaxY := _y_max_ end;

procedure SetColor;
begin _color_:=c end;

function GetColor:byte;
begin GetColor:=_color_ end;

procedure SetBkColor(c:byte);
begin _bk_color_:=c end;

function GetBkColor:byte;
begin GetBkColor:=_bk_color_ end;

procedure PrintAt; assembler;


[ poziţionare cursor pentru afişare cu PrintS sau Print ]
asm PUSH BP; MOV AH,$02; MOV DH,y; MOV DL,x;
MOV BH,0; INT $10; POP BP
end;

procedure Advance; assembler;


[ avansare poziţie cursor pentru scriere ]
asm PUSH BP; MOV AH,$03; MOV BH,$00; INT $10;
INC DL; MOV AH,$02; MOV BH,$00; INT $10; POP BP
end;

procedure PrintC(a:char);
[ afişează în poziţia curentă a cursorului de scriere un caracter a ]
var c:integer;

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 Line(x0,y0,x1,y1: Integer);


[ trasează o linie între (x0,y0) şi (x1,y1) ]
var a,b,c,a2,b2,ab2,ds,xinc,yinc,u,v,i: Integer;
begin
c:=GetColor; u:=x0; v:=y0;
a:=abs(y1-y0); b:=abs(x1-x0); a2:=a*2; b2:=b*2;
if x0<x1 then xinc:=1 else xinc:=-1;
if y0<y1 then yinc:=1 else yinc:=-1;
PutPixel(u,v,c);
if (b>a) then begin
ds:=a2-b; ab2:=a2-b2;
for i:=1 to b do begin
if (ds>=0) then begin Inc(v,yinc);Inc(ds,ab2) end
else Inc(ds,a2);
Inc(u,xinc); PutPixel(u,v,c)
end
end
else begin ds:=b2-a; ab2:=b2-a2;
for i:=1 to a do begin
if (ds>=0) then
begin Inc(u,xinc); Inc(ds,ab2) end
else Inc(ds,b2);
Inc(v,yinc); PutPixel(u,v,c)
end
end
end;

procedure CLine(x0,y0,x1,y1:integer; c:byte);


[ trasează o linie de culoare c; nu afectează culoarea curentă ]
var cc:byte;
begin cc:=GetColor; SetColor(c); Line(x0,y0,x1,y1); SetColor(cc) end;

procedure Rectangle(x11,y11,x22,y22:integer);
[ trasează un dreptunghi de la (x11,y11) la (x22,y22) ]
var x1,y1,x2,y2:integer;
begin
x1 := min(x11,x22); y1 := min(y11,y22);

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;

procedure SetWriteMode(modul: Byte); [ alege modul de scriere ]


begin WriteMode := modul end;

procedure CloseGraph; [ părăseşte modul grafic ]


begin SetVideoMode(3) end;

procedure Circle(x,y,r: Integer);


[ trasează un cerc de centru (x,y) şi rază r, în culoarea curentă ]
var a,b,c,d: LongInt;
begin a:=r;b:=0;c:=r;d:=0;
repeat
PutPixel(x+a,y+b,_color_); PutPixel(x-a,y+b,_color_);
PutPixel(x-a,y-b,_color_); PutPixel(x+a,y-b,_color_);
PutPixel(x+b,y+a,_color_); PutPixel(x-b,y+a,_color_);
PutPixel(x-b,y-a,_color_); PutPixel(x+b,y-a,_color_);
Inc(d,b+b+1); Inc(b);
if d>c then begin inc(c,a+a+1);dec(a) end;
until a<b
end;

procedure Oval(xc,yc,a,b: Integer);


[ trasează o elipsă, cu centrul în (xc,yc), de semiaxe a şi b ]
[ trasarea este rapidă; se folosesc doar adunări, scăderi, înmulţiri ]
[ această procedură nu figura în unit-ul MCGA prezentat la finele ]
[ lucrării "Grafică în OOP ...şi nu numai..." ]
var x,y: Integer; aa,aa2,bb,bb2,d,dx,dy: LongInt;
begin
x := 0; y := b;
aa := LongInt(a) * a; aa2 := 2 * aa;
bb := LongInt(b) * b; bb2 := 2 * bb;
d := bb - aa * b + aa div 4; dx := 0; dy := aa2 * b;
PutPixel(xc,yc-y,_color_); PutPixel(xc,yc+y,_color_);
PutPixel(xc-a,yc,_color_); PutPixel(xc+a,yc,_color_);
while dx < dy do
begin
if d>0 then begin Dec(y); Dec(dy,aa2); Dec(d,dy) end;
Inc(x); Inc(dx,bb2); Inc(d,bb+dx);
PutPixel(xc+x,yc+y,_color_); PutPixel(xc-x,yc+y,_color_);
PutPixel(xc+x,yc-y,_color_); PutPixel(xc-x,yc-y,_color_)
end;
Inc(d, (3 * (aa-bb) div 2 - (dx+dy)) div 2);
while y>0 do begin
if d<0 then begin Inc(x); Inc(dx,bb2); Inc(d,bb+dx) end;
Dec(y); Dec(dy,aa2); Inc(d,aa-dy);
PutPixel(xc+x,yc+y,_color_); PutPixel(xc-x,yc+y,_color_);
PutPixel(xc+x,yc-y,_color_); PutPixel(xc-x,yc-y,_color_)
end
end;

procedure ReverseX(x1,y1,x2,y2: Integer);


[ oglindeşte faţă de verticala din centru, o zona dreptunghiulară ]

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;

procedure ReverseY(x1,y1,x2,y2: Integer);


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

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

unit ViewBMP;

interface

uses Dos;

procedure LoadPackBMPFile(x, y: LongInt; NameCode: PathStr);


procedure SavePackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr);
procedure LoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr);
procedure SaveUnPackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr);
procedure SLoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr);
procedure PLoadUnPackBMPFile(x,y: LongInt; nr: Integer; NameCode:
PathStr);
procedure SPLoadUnPackBMPFile(x,y: LongInt; nr: Integer; NameCode:
PathStr);
function BMPError: Boolean;

implementation

uses BMPTypes, MCGA;

var
testH: BitMapFileHeader;
testI: BitMapInfo;
cRed, cGreen, cBlue: byte;
cRGB: RGBQuad;
cReg: byte;

115
fBMP: file;
iBMP, jBMP: LongInt;
memBMP: byte;
readByte: byte;
LineBuff: array[0..639] of byte;
Error: Boolean;

procedure LoadPackBMPFile(x, y: LongInt; NameCode: PathStr);


begin
Assign(fBMP, NameCode);
Reset(fBMP, 1);
BlockRead(fBMP, testH, SizeOf(testH));
BlockRead(fBMP, testI.H, SizeOf(testI.H));
if testI.H.BitCount < 4 then
begin
SetVideoMode(3);
Error := True;
Exit;
end
else if testI.H.ClrImportant >= 0 then
begin
for iBMP := 0 to 15 do
begin
BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP]));
testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4;
testI.C[iBMP].Green := testI.C[iBMP].Green div 4;
testI.C[iBMP].Red := testI.C[iBMP].Red div 4;
end;
end;
for iBMP := 0 to 5 do
SetColorRegister(iBMP, testI.C[iBMP].Red, testI.C[iBMP].Green,
testI.C[iBMP].Blue);
SetColorRegister(20, testI.C[6].Red, testI.C[6].Green,
testI.C[6].Blue);
SetColorRegister(7, testI.C[7].Red, testI.C[7].Green, testI.C[7].Blue);
for iBMP := 56 to 63 do
SetColorRegister(iBMP, testI.C[iBMP - 48].Red, testI.C[iBMP -
48].Green, testI.C[iBMP - 48].Blue);
for iBMP := 0 to testI.H.Height - 1 do
begin
jBMP := 0;
repeat
BlockRead(fBMP, readByte, 1);
memBMP := readByte;
readByte := memBMP shr 4;
PutPixel(x + jBMP, y + testI.H.Height - iBMP - 1, readByte);
Inc(jBMP);
memBMP := memBMP and 15;
PutPixel(x + jBMP, y + testI.H.Height - iBMP - 1, memBMP);
Inc(jBMP);
until jBMP = testI.H.Width;
end;
Close(fBMP);
end;

procedure SavePackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr);


begin
Assign(fBMP, NameCode);
Rewrite(fBMP, 1);
testH.Types[1] := 'B';

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;

procedure LoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr);


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

procedure PLoadUnPackBMPFile(x, y: LongInt; nr: Integer; NameCode:


PathStr);
begin
Assign(fBMP, NameCode);
Reset(fBMP, 1);
BlockRead(fBMP, testH, SizeOf(testH));
BlockRead(fBMP, testI.H, SizeOf(testI.H));
if testI.H.BitCount < 4 then
begin
SetVideoMode(3);
Error := True;
Exit
end
else if (testI.H.BitCount = 8) and (testI.H.ClrImportant >= 0) then
begin
for iBMP := 0 to 255 do
begin
BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP]));
testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4;
testI.C[iBMP].Green := testI.C[iBMP].Green div 4;

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;

procedure SLoadUnPackBMPFile(x, y: LongInt; NameCode: PathStr);


begin
Assign(fBMP, NameCode);
Reset(fBMP, 1);
BlockRead(fBMP, testH, SizeOf(testH));
BlockRead(fBMP, testI.H, SizeOf(testI.H));
if testI.H.BitCount < 4 then
begin
SetVideoMode(3);
Error := True;
Exit
end
else if (testI.H.BitCount = 8) and (testI.H.ClrImportant >= 0) then
begin
for iBMP := 0 to 255 do
begin
BlockRead(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP]));
testI.C[iBMP].Blue := testI.C[iBMP].Blue div 4;
testI.C[iBMP].Green := testI.C[iBMP].Green div 4;
testI.C[iBMP].Red := testI.C[iBMP].Red div 4;
end;
end;

for iBMP := 0 to testI.H.Height - 1 do


begin
BlockRead(fBMP, LineBuff, testI.H.Width);
for jBMP := 0 to testI.H.Width - 1 do
Mem[$A000: jBMP + x +
320 * y + 320 * (testI.H.Height - iBMP - 1)] :=
LineBuff[jBMP];
end;
Close(fBMP)
end;

procedure SPLoadUnPackBMPFile(x, y: LongInt; nr: Integer; NameCode:


PathStr);
begin
Assign(fBMP, NameCode);
Reset(fBMP, 1);
BlockRead(fBMP, testH, SizeOf(testH));
BlockRead(fBMP, testI.H, SizeOf(testI.H));
if testI.H.BitCount < 4 then

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;

procedure SaveUnPackBMPFile(x1, y1, x2, y2: LongInt; NameCode: PathStr);


begin
Assign(fBMP, NameCode);
Rewrite(fBMP, 1);
testH.Types[1] := 'B';
testH.Types[2] := 'M';
testH.Size := (x2 - x1 + 1) * (y2 - y1 + 1) + 1078;
testH.Reserved1 := 0;
testH.Reserved2 := 0;
testH.OffBits := 1078;
testI.H.Size := 40;
testI.H.Width := x2 - x1 + 1;
testI.H.Height := y2 - y1 + 1;
testI.H.Planes := 1;
testI.H.BitCount := 8;
testI.H.Compression := 0;
testI.H.SizeImage := 0;
testI.H.XPelsPerMeter := 0;
testI.H.YPelsPerMeter := 0;
testI.H.ClrUsed := 256;
testI.H.ClrImportant := 256;
BlockWrite(fBMP, testH, SizeOf(testH));
BlockWrite(fBMP, testI.H, SizeOf(testI.H));
for iBMP := 0 to 255 do
begin
ReadColorRegister(iBMP, cRed, cGreen, cBlue);
testI.C[iBMP].Blue := cBlue * 4;
testI.C[iBMP].Green := cGreen * 4;
testI.C[iBMP].Red := cRed * 4;
testI.C[iBMP].Reserved := 0;
BlockWrite(fBMP, testI.C[iBMP], SizeOf(testI.C[iBMP]));
end;
for iBMP := 0 to testI.H.Height - 1 do
begin

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

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


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

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

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

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


2004.

6. Tănăsescu, A., Constantin, R., Marinescu, I. D., Busuioc, L. - Grafică asistată.


Programe FORTRAN pentru reprezentări geometrice, Editura Tehnică, Bucureşti,
1989.

122

S-ar putea să vă placă și