Simson program

A Simson program bemutatása.

Bevezetõ

A program jellemzése

Ez egy interaktív matematikai demonstrációs program, amelynek az a célja, hogy szemléltesse az ábrázoló geometriában a háromszögre vonatkozó nevezetes tételeket (pl. beírt kör, magasságpont). A Simson-t a http://simson.ini.hu weboldalról lehet letölteni (természetesen ingyenesen). A programot FreePascal-ban írtam, és a Simson program 800x600-as felbontásban dolgozik 16 bit színnel. Ehhez az SVGA grafikához felhasználtam a Venom Software-nek a VGFX grafikáját. Ezt a www.venomsoftware.de oldalról lehet letölteni ingyenesen, de a CodeX-ban is megtalálható.
És most, ha még nem néztétek volna meg magát a programot, akkor rajta! Most tegyétek meg!
Ha nem teszitek meg, akkor esetleg nem értitek meg, hogy mirõl beszélek.

A fejlesztõkörnyezetrõl

Amint már mondtam, a programot FreePascal-ban írtam. Gondolom sokan ismeritek a Turbo Pascal-t. Nos, a FreePascal tulajdonképpen egy az egyben ugyanaz, mint a Turbo Pascal, csak annyi különbséggel, hogy a FreePascal Windows-os*, így korlátlan memóriát lehet lefoglalni, több beépített unit található, és akár Windows-os ablakokat is lehet készíteni. A FreePascal fejlesztõkörnyezetet a www.freepascal.org weboldalról lehet letölteni ingyenesen (ahogy azt a neve is mutatja ).
*de van MS-DOS operációs rendszerre is FreePascal, hogy egész pontos legyek

A grafikáról

A VGFX grafika, amit használ a programom, megtalálható a forraskod nevû mappában. Ez a grafika tartalmazza a "vgfx"-szel kezdõdõ fájlokat, a fonts.fnt-t, a bigfile2.pp-t, valamint az fpcx.dll-t. (A simson.pp meg nyilván a Simson program forráskódja.)
Nekünk ebbõl a sok unit-ból igazán csak a vgfx_sprites és vgfx_2d unit-okat kell tudni használni.

Mi történik a program indításakor?


Hát hogy mi? Azt én sem tudom. Na jó, csak vicceltem. Akkor hát vágjunk bele Nyissuk meg a simson.pp fájlt, és észre lehet venni, hogy a program legelõször az init_simson eljárást hívja meg. Ez az eljárás tartalmaz elõször egy mp_kiszamitas eljárást.

//a gép gyorsaságát jellemzõ szám az mp
procedure mp_kiszamitas;
var
elotte,utana:longint;
begin
{megnézi, hogy a gép 3 másodperc alatt meddig tud elszámolni, és ezáltal méri fel az adott gép gyorsaságát}
elotte:=gettickcount;
mp:=0;
repeat
inc(mp);
until gettickcount-elotte>=3000;

utana:=gettickcount;
gyorsitas:=(200*1000000)/mp;
kiegyenlito:=Snevezoallando/mp;
end;

Ez egy cikluson belül növel egy változót 3 másodpercig azért, hogy megtudjuk, milyen gyors a számítógép (ezért indul el olyan sokára a program). Ez az animációnál fontos, hogy minden gépen ugyanolyan sebességgel menjen a pont. Ezután jön a fill_rgbtocolor. Ez feltölti az rgbtocol tömböt a megfelelõ értékekkel. A Simson program 16 bit színnel dolgozik, a 16 bit meg egyenlõ egy word típussal. És erre az rgbtocol tömbre azért van szükség, mert a VGFX unitokban, legfõkébb a vgfx_2d unitban vannak olyan eljárások, ahol színként nem a három színkomponenst (RGB), hanem egyetlen word-öt kell megadni. És ezekkel nagyon nehéz dolgozni. Pl. ha azt mondanám, hogy mondd meg a piros színnek az értékét word-ben (0..65535), akkor nem tudnád (én sem). De ugyanezt RGB-vel nagyon egyszerû: R=255, G=0, B=0 ez a piros leírása tehát RGB-vel.

Például:
procedure PutPackedPixel(Dest:VirtualWindow; x,y:longint; Color:word); {ez az eljárás egy Color színû pontot rajzol a Dest nevû képre}

Tehát ez a rgbtocol tömb egy "átváltásra" való a Word és az RGB között. Ezután pedig ilyenek jönnek, hogy:

simson_szog:=0;
animacio:=true;
play:=false;
{stb...}

Ezek az értékadások csak a program "helyes" indulásához szükségesek. Pl. a simson_szog változó határozza meg, hogy a Simson pont hol van a körön, aztán a play dönti el, hogy mozogjon-e a pont, stb...
Majd a gomb tömböt kell feltölteni. Ez határozza meg, hogy a spirál alatti gombok kék színû téglalapjai mettõl-meddig legyenek. El kell mondanom, hogy legalább 1 órát eltöltöttem azzal, mire beállítottam a gombok helyeit... Itt nem fogok minden egyes változót leírni, hogy mi mit csinál, mert nem férne el, ezért ezentúl csak a fontosabb részeket írom le. A fill_mt eljárás feltölti az mt tömböt, ami meghatározza, hogy az adott menünél milyen egyenesek, körök, pontok látszódjanak, ill. milyen színûek legyenek. És most jön a grafika inicializálása:

Window_RegisterClass(WIN_Normal); {elõször bejegyezzük az ablakunk osztályát a Win_Normal stílussal}
Window_Main_Handle:=Window_CreateClass(ApplicationID,WIN_Normal); {létrehozzuk a bejegyzett ablakosztályunkat, aminek ApplicationID lesz a címe}
CheckWMCreate; {ellenõrizzük, hogy létezik-e az ablakunk}
InitGraph(width,height); {inicializáljuk a grafikát, itt vált át 800x600-ba}
Load_Fonts('Fonts.fnt'); {betöltjük a betûket}

Ezután el kell mondanom, hogy mi az a VirtualWindow, mert már sokan kíváncsiak vagytok biztosan. Ez egy virtuális kép, amire lehet rajzolni, ill. ki lehet tenni a képernyõre. A Load_Sprite('menu.sim','',sprite01) eljárás például betölti a sprite01 nevû képbe ("ablakba") a menu.sim képet. Most nyilván nem értitek, hogy miért ilyen fura képtípust használok, és miért nem JPG-et, vagy BMP-t. Hát azért, mert ezt nehezebb felcrack-elni, hogy ne írják át a szerzõ nevét . Az Init_VW(Result,width,height,true) eljárás pedig memóriát foglal a Result képnek. Ha ennek az eljárásnak az utolsó paramétere true, akkor a kép feltöltõdik csupa 0-kal, vagyis fekete képet kapunk. A ScaleSprite(sprite01,background) pedig átmásolja a sprite01-et a background-ra, de úgy, hogy "rástretcheli". Ezután a kill_vw eljárás pedig a paraméterként megadott kép által lefoglalt memóriát szabadítja fel. És az init_simson eljárással készen is vagyunk.

Lássuk, mi jön ezután: intro. Ez az eljárás elõször a background képet "öli meg", és helyére betölti a sim.sim-et, vagyis a program elején látható körmozgást végzõ feliratot tölti be. A cursorvisible eltünteti az egeret. A gettickcount azt adja meg, hogy mióta meg a számítógépünk. Ezután egy repeat-until cilus jön, ami kereken 7 másodpercig tart, de hamarabb is befejezõdhet, ha pl. lenyomjuk az egér bal gombját, vagy bezártuk a programot (ezek a feltételek az until résznél vannak). A cikluson belül az történik, hogy a PutClippedSprite eljárás itt rárajzolja a background képet (vagyis a Simson feliratot) a result képre, majd a Flip_DD(Result.VWOffset) kirajzolja a Result képet a képernyõre. A GetAllMessages begyûjti az összes Windows által küldött rendszerüzeneteket (pl. gomblenyomás, egérkattintás, stb.), a HandleMinimized pedig ellenõrzi, hogy a programot lekicsinyítették-e a tálcára, mert ha igen, akkor a program ideiglenesen felfüggeszti magát, hogy ne használja feleslegesen a processzort...
Ezután "megöljük" a background képet (a Simson feliratot), és helyére betöltjük a menu.sim képet, vagyis a spirálfüzetes képet, és az intro eljárás legvégén pedig láthatóvá tesszük az egeret a CursorVisible(true) eljárással.
Így már át is vettük a program elején levõ 2 eljárást. És most nézzük át a fõprogramot:

A fõprogram

// FÕPROGRAM KEZDETE
BEGIN
init_simson;
intro;

REPEAT // FÕCIKLUS!
egerallapotok;
if sugob=false then sfirst:=true;

if not sugob then
begin
szamolasi_Qeljarasok;
elemek_kirajzolasa;
end
else begin

if sfirst then begin
kill_vw(background);
load_sprite('sugo.sim','',background);
putsprite(result,background,0,0);
end;

if (not m_ltomb[2]) and (m_l) then sugob:=false;

sfirst:=false;
if not sugob then
begin
kill_vw(background);
load_sprite('menu.sim','',background);
end;

end;

adjustspritebrightness(result,fenyforras,(fenyforras shl 1),fenyforras);
Flip_DD(Result.VWOffet); {kirajzol a képernyõre!}
GetAllMessages;
HandleMinimized;

UNTIL not ApplicationRunning or gombkattint(1); {FÕCIKLUS vége!}

cursorvisible(false);
lampale;
memoria_felszabaditas; {grafika bezárása és "memóriatisztítás"}
END.
//FÕPROGRAM VÉGE!

A program elején levõ elsõ kettõ eljárást már át is vettük, úgyhogy nézzük, mi történik ezután. A fõciklus mindaddig ismitlõdik, amíg be nem zárjuk a programot, vagy rá nem kattintunk a Kilépre, vagyis az egyes számú gombra (ezek az until résznél vannak). A fõciklusban legelõször az egerallapotok eljárás hívódik meg. Ez vizsgálja meg, hogy kattintottunk-e az egérrel. A sugob nevû változó dönti el, hogy a Súgót lássuk-e, vagy az "igazi" programrész fusson. És most haladjunk tovább. Itt jön egy if elágazás, aminek a then része akkor hajtódik végre, ha nem vagyunk benne a súgóban. Ekkor a meghívjuk a szamolasi_Qeljarasok nevû eljárást, ami kiszámolja a háromszög összes nevezetes pontját, egyenesét, körét, paraboláját. Majd ezután az elemek_kirajzolasa kirajzolja a képernyõre azt, ami kell nekünk. Tehát a programnak a "magja" az, hogy egy fõcikluson belül kiszámoljuk az összes egyenes, kör, ... pozícióját, azaz helyét, és aztán kiválogatjuk azt, hogy miket kell kirajzolni. Pofonegyszerû, nem igaz?! De még nincs vége az if elágazásnak. Az else rész pedig akkor hívódik meg, ha benne vagyunk a súgóban. Gondolom, észrevettétek, hogy kihagytam az sfirst változó elmagyarázását. Most bepótlom: Ez akkor igaz, ha elõször lépünk be a súgóba. Amint a ciklus már másodjára fut le a súgóban, akkor már false értéke lesz. Tehát a lényeg az, hogy amikor elõször lépünk be a súgóba, akkor lefut ez a programkód:

if sfirst then begin
kill_vw(background);
load_sprite('sugo.sim','',background);
putsprite(result,background,0,0);
end;

Ezzel "megöljük" a spirálfüzetes háttérképet, és helyére betöltjük a sugo.sim nevû fájlból a súgó képet, majd a PutSprite eljárással rárajzoljuk a result-ra. Majd ez a programkód gondoskodik arról, hogyha kattintottunk, akkor a fõciklus következõ lefutásakor visszatérjünk a súgóból:

if (not m_ltomb[2]) and (m_l) then sugob:=false;

Az m_l egy boolean, ami akkor igaz, ha az egér gombja le van nyomva. És azért nem elég csupán a második feltétel az if utasításban, mert ha csak az lenne ott, akkor a Súgó gombjának lenyomásakor vibrálna a képernyõ azért, mert egyik pillanatban belépnénk a súgóba, aztán már jönnénk is ki. Ezért bevezettem egy m_ltomb nevû tömböt, ami az egér bal gombjának elõzõ állapotait tárolja. Ez egy 5 elemû tömb, az elsõ eleme az aktuális egér állapotát jelenti, a második eleme pedig az eggyel korábbit. És a (not m_ltomb[2]) feltétel akkor igaz, ha az egér bal gombjának eggyel elõbbi állapotában nem volt lenyomva, tehát ez a két feltétel csakis akkor igaz, ha épp most lett lenyomva az egér. Ezzel megoldjuk a vibrálás problémáját.

Ezután az sfirst:=false azért kell, hogy a fõciklus következõ lefutásakor ne hajtódjon végre az a programkód, ami csak a súgóba belépéskor hajtódik végre. Ha ezzel megvolnánk, akkor jön ez:

if not sugob then
begin
kill_vw(background);
load_sprite('menu.sim','',background);
end;

Ez pedig a súgóból való kilépéskor hajtódik végre, és a lényege, hogy "megöli" a background nevû képet (vagyis a Súgó képét), és helyére betölti a spirálfüzetes képet. Aztán a fõcikluson belül van egy ilyen programkód, ami mindig végrehajtódik (függetlenül attól, hogy benne vagyunk-e a súgóban, vagy nem):

adjustspritebrightness(result,fenyforras,(fenyforras shl 1),fenyforras);
Flip_DD(Result.VWOffet); {kirajzol a képernyõre!}
GetAllMessages;
HandleMinimized;

Az elsõ eljárás, vagyis az adjustspritebrightness gondoskodik a képernyõ világosságáról. Ezt pedig a fenyforras nevû változó szabályozza. Ha ennek a változónak az értéke -31, akkor teljesen fekete a monitor, és minél nagyobb, annál világosabb van. Ha az értéke 0, akkor normális a fényerõ, és ha értéke +31, akkor pedig minden fehér színû.
A Flip_DD eljárás kirajzolja a Result nevû képet a képernyõre (amint azt a megjegyzésben is leírtam). Aztán a GetAllMessages begyûjti az összes Windows által küldött rendszerüzenetet, ami ahhoz kell, hogy megvizsgáljuk azt, hogy történt-e billentyûlenyomás, egérkattintás, stb. És a HandleMinimized megvizsgálja, hogy le lett-e kicsinyítve az ablakunk, mert ha igen, akkor felfüggeszti a program futását ideiglenesen.

Amikor befejezõdött a fõciklus futása, mert rákattintottunk a Kilép-re (egyes számú gombra), vagy bezárták a programunkat, akkor ezután jön ez a programkód:

cursorvisible(false);
lampale;
memoria_felszabaditas; {grafika bezárása és "memóriatisztítás"}

A cursorvisible itt eltünteti az egeret, majd a lampale eljárás "lekapcsolja a villanyt", komolyabban megfogalmazva fokozatosan elsötétíti a képernyõt. És a legeslegvégsõ eljárás, vagyis a memoria_felszabaditas bezárja a grafikát, és felszabadítja a képek által lefoglalt memóriát.

 

Egy kis matek...

Amint már mondtam, a program "magja", azaz kernelje az, hogy a fõcikluson belül kiszámoljuk az összes objektum (pont, egyenes, kör, parabola) pozícióját, és aztán kirajzoljuk. Ez a két eljárás hajtja végre ezeket a feladatokat:

szamolasi_Qeljarasok;
elemek_kirajzolasa;

És most erre fogok kitérni, ezt fogom részletezni. A program elején levõ type deklarációknál vannak a geometriai objektumok típusai. Pl. pont_type, szakasz_type, egyenes_type, kor_type és parabola_type. Például így néz ki a pont_type deklarációja:

type
pont_type = record
x,y:real;
kx,ky,meret:longint;
red,green,blue:byte;
aktiv,letezes:boolean;
end;

Az x, és y lebegõpontos számok határozzák meg, hogy hol legyen a pont, a kx, és ky pedig az x, és y számoknak kerekített értékei. A red, green és blue változók szabályozzák a pont színét. Az aktiv dönti el, hogy látszódjon-e a pont a képernyõn, a letezes változó pedig az adott pont létezését tárolja. Ja, és amit kihagytam, a meret pedig egyértelmûen a pont méretéért "felelõs".
Hasonlóan elmondhatnám a szakasz_type, egyenes_type, ... típusokat is, hogy mi miért kell, de a hely hiánya miatt ezt mellõzzük. Tehát összefoglalóan azt kell tudni a pont_type, szakasz_type, egyenes_type, kor_type és parabola_type típusokról, hogy a képernyõn látható geometriai pontok, szakaszok, egyenesek, körök és parabolák pozícióit, színeit, nagyságát, stb. tárolja. És most továbblépünk.

A típus-deklarációk után jönnek a változók leírása. A geometriai pontok, szakaszok, ... ezekben a tömbökben vannak:

var
Pont:array[1..pontok_szama] of pont_type;
Szakasz:array[1..szakaszok_szama] of szakasz_type;
Egyenes:array[1..egyenesek_szama] of egyenes_type;
Kor:array[1..korok_szama] of kor_type;
Parabola:array[1..parabolak_szama] of parabola_type;

Tehát itt van az összes geometriai objektum definiálva. A simson.pp fájlban ezután jön egy nagyon hosszú megjegyzés. Valahogy így néz ki:

{
PONT:

1: a 3szög A pontja <-- bázispontok
2: a 3szög B pontja
3: a 3szög C pontja

stb....
}

Itt írom le, hogy a tömbökben melyik elem mit jelent. Pl. a Pont nevû tömb elsõ eleme, azaz a Pont[1] a háromszög A pontja, a Pont[2] pedig a háromszög B pontja. Direkt nem másoltam be a forráskódból ide az egészet, mert nagyon hosszú lenne. De ha megnyitjátoka simson.pp fájlt, akkor ott látni fogjátok. Tehát most már elértünk oda, hogy tudjuk azt, milyen változókban tárolja a program a geometriai objektumok pozícióját. Ennyi kitérõ után térjünk vissz a szamolasi_Qeljarasok nevû eljáráshoz! Ez az eljárás számolja ki az összes geometriai objektum pozícióját. Ez az eljárás tartalmaz rengeteg Q-val kezdõdõ eljárást. Ezek számolják ki a pontok, szakaszok, ... helyeit. Pl. a Qketpont_felezoje eljárás két pont felezési pontját számolja ki. Ez az eljárás 3 paraméteres, az elsõ kettõbe a 2 adott pontnak a számát kell beírni, a harmadikba meg a felezési pontot. Tehát ha meghívjuk ezekkel a paraméterekkel az eljárást: Qketpont_felezoje(1,2,4) akkor a 4-es számú pont lesz az 1-es és 2-es számú pont felezési pontja. Világos, ugye? Ezzel át is vettük a szamolasi_Qeljarasok-at.
Haladjunk tovább az elemek_kirajzolasa nevû eljárás pedig kirajzolja a pontokat, szakaszokat, egyeneseket, egyszóval a geometriai elemeket (avagy objektumokat). Az eljárás nagyon rövid, és egyszerû is. A lényege, hogy egy i ciklusváltozóval végigmegyünk az Pont, Szakasz, Egyenes, Kor és Parabola tömbök összes elemén úgy, hogy közben kirajzoljuk. Így néz ki tehát az elemek_kirajzolasa eljárás:

{Ez az eljárás gondoskodik arról, hogy kirajzolódjanak az objektumok (azaz geometriai elemek) }
procedure elemek_kirajzolasa;
begin
pontvilagosit; {ez az eljárás felelõs azért, hogyha rámegyünk a háromszög valamelyik csúcsára, akkor az kivilágosodik}

for i:=1 to parabolak_szama do ParabolaArnyekKirak(i);
for i:=1 to korok_szama do KorArnyekKirak(i);
for i:=1 to egyenesek_szama do EgyenesArnyekKirak(i);
for i:=1 to szakaszok_szama do SzakaszArnyekKirak(i);
for i:=pontok_szama downto 1 do PontArnyekKirak(i);

for i:=1 to parabolak_szama do ParabolaKirak(i);
for i:=1 to korok_szama do KorKirak(i);
for i:=1 to egyenesek_szama do EgyenesKirak(i);
for i:=1 to szakaszok_szama do SzakaszKirak(i);
for i:=pontok_szama downto 1 do PontKirak(i);

pontmozgat; {ez az eljárás felelõs azért, hogy lehessen mozgatni a háromszög pontjait}

PutHcSprite(result,sprite01,0,0,0);
for i:=1 to 16 do GombKirak(i);

if GombKattint(11) then sugob:=true; {súgó meghívása}
end;

Az elemek_kirajzolasa eljárás elején levõ pontvilagosit eljárással kivilágosítjuk azt a háromszög-csúcspontot, amelyiken rajta van az egér. Aztán ez a rengeteg for ciklus azért kell, hogy kirajzoljuk a Sprite01 képre (avagy ablakra) a geometriai objektumokat. Tehát például a ParabolaArnyekKirak eljárás a paraméterként megadott parabolát rajzolja ki. Ez a sok "para-szó" elág bután hangzik, nem?!
És direkt úgy csináltam meg a rajzolás sorrendjét, hogy elõször a geometriai objektumok árnyékát rajzolom ki, és csak aztán jönnek a színes objektumok. Hogy miért? Hát azért, mert így azt a hatást váltom ki, hogy a pontok, szakaszok, ... alatt vannak az árnyékok. Világos, ugye?
A for ciklusok után jön egy pontmozgat nevû eljárás, ami arról gondoskodik, hogy mozogjanak a háromszög csúcsai (de csak akkor, ha az egérrel arréb raktuk, mert magától nem mozdulnak el). Ezután a PutHcSprite(result,sprite01,0,0,0) eljárás rárajzolja a Sprite01 képet a Result nevû képre, de úgy, hogy a feketét kihagyja. Az eljárás nevében a "Hc" azt jelenti, hogy "HideColor", azaz színeltûntetés. Az eljárás utolsó paraméterében kell megadni azt a színt, amit ki kell hagyni. Hát itt pont a feketét kell kihagyni, és a fekete szín egyenlõ nullával. Ha ezt nem tennénk meg, akkor egy nagy fekete képet kapnánk. Így meg a fekete helyén a négyzetrács látható.
Ezután ismét jön egy for ciklus, ami a gombokat színezi át kékké, sárgává, vagy szürkévé. És még van egy if utasítás, aminek a then része akkor hajtódik végre, ha rákattintunk a Súgó gombra, és ekkor a sugob nevû boolean típusú változó értékét true-ra állítjuk, hogy a fõciklus következõ lefutásakor a súgó rész jöjjön be.

Ha bármilyen kérdésetek vagy hozzászólásotok van a Simson programmal kapcsolatban, akkor nyugodtan írjatok nekem a simson-program@axelero.hu címre!

Võneki Balázs - simson-program@axelero.hu