A Simson program bemutatása.
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.
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:
// 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.
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!