Difference between revisions of "Peg Solitaire tutorial/fi"

From Lazarus wiki
Jump to navigationJump to search
(Created page with "{{Peg_Solitaire_tutorial}} Tämä opetusohjelma on toinen Lazarus opetusohjelma, jonka tavoitteena on ottaa käyttöön Lazaruksen sovelluskehityksen perusteet. On parasta al...")
 
 
Line 33: Line 33:
 
Peruselementit PegSolitaire laudalla ovat marmorit, levyn rakenne ja tyhjät paikat. Simuloidaan yksinkertaisella matriisilla, jossa paikat ovat pelin solujen tyyppisiä (tyhjä, pelinappula ja ei pääse). Ja tämä kaikki kiteytetään luokkaan, joka käsittelee kaikkea tietojen manipulointia.
 
Peruselementit PegSolitaire laudalla ovat marmorit, levyn rakenne ja tyhjät paikat. Simuloidaan yksinkertaisella matriisilla, jossa paikat ovat pelin solujen tyyppisiä (tyhjä, pelinappula ja ei pääse). Ja tämä kaikki kiteytetään luokkaan, joka käsittelee kaikkea tietojen manipulointia.
  
* Lisää seuraava koodi PegDatastructures käännösyksikköön ( ''uses''-lausekkeen jälkeen juuri ennen ''implementation'' osiota):
+
* Lisää seuraava koodi PegDatastructures käännösyksikköön ( ''[[Uses/fi|uses]]''-lausekkeen jälkeen juuri ennen ''[[Implementation/fi|implementation]]'' osiota):
  
<source>
+
<syntaxhighlight lang="pascal">
 
const
 
const
 
   C_MAX = 7;  // Max board size: 7x7
 
   C_MAX = 7;  // Max board size: 7x7
Line 52: Line 52:
 
     constructor Create(const pSize: TCellNums);
 
     constructor Create(const pSize: TCellNums);
 
   end;
 
   end;
</source>
+
</syntaxhighlight>
  
On kohtuullista olettaa, että muu koodi, joka aikoo käyttää tätä luokaa tarvitsee pääsyn solujen sisältöön (eli PegCells). Tapa käsitellä tätä on joko määrittelemällä joukko funktioita jotka hakee ja vie soluihin tai määritellä ns taulukko-ominaisuus. Valitaan jälkimmäinen lähestymistapa ja lisätään seuraava rivi TPegSolitaire luokan public osioon:
+
On kohtuullista olettaa, että muu koodi, joka aikoo käyttää tätä luokkaa tarvitsee pääsyn solujen sisältöön (eli PegCells). Tapa käsitellä tätä on joko määrittelemällä joukko funktioita jotka hakee ja vie soluihin tai määritellä ns taulukko-ominaisuus. Valitaan jälkimmäinen lähestymistapa ja lisätään seuraava rivi TPegSolitaire luokan public osioon:
<source>
+
<syntaxhighlight lang="pascal">
 
property Cell[const pRow, pCol: TCellNums]: TCellType;
 
property Cell[const pRow, pCol: TCellNums]: TCellType;
</source>
+
</syntaxhighlight>
 
* Sijoita tekstikursori rakentajan eli constructor:n riville.
 
* Sijoita tekstikursori rakentajan eli constructor:n riville.
 
* Paina {{keypress|Ctrl|Shift|C}}: ohjelman kehitysympäristö luo rakentajan rungon (kuten aiemmissa esimerkeissä), mutta se myös luo tyhjän rungon kahdelle metodille, jotka antavat pääsyn ''Cell''-ominaisuuden kautta ''PegCells'' taulukkoon.
 
* Paina {{keypress|Ctrl|Shift|C}}: ohjelman kehitysympäristö luo rakentajan rungon (kuten aiemmissa esimerkeissä), mutta se myös luo tyhjän rungon kahdelle metodille, jotka antavat pääsyn ''Cell''-ominaisuuden kautta ''PegCells'' taulukkoon.
 
* Funktio GetCell noutaa tiedot PegCells taulukosta. Lisää seuraava koodi tähän funktioon:  
 
* Funktio GetCell noutaa tiedot PegCells taulukosta. Lisää seuraava koodi tähän funktioon:  
<source>
+
<syntaxhighlight lang="pascal">
 
   result := PegCells[pRow,pCol];
 
   result := PegCells[pRow,pCol];
</source>
+
</syntaxhighlight>
 
* Aliohjelma SetCell sijoittaa dataa PegCells taulukkoon. Lisää seuraava koodi tähän aliohjelmaan:
 
* Aliohjelma SetCell sijoittaa dataa PegCells taulukkoon. Lisää seuraava koodi tähän aliohjelmaan:
<source>
+
<syntaxhighlight lang="pascal">
 
   PegCells[pRow,pCol] := AValue;
 
   PegCells[pRow,pCol] := AValue;
</source>
+
</syntaxhighlight>
 
* Ja viimeistellä rakentaja Create. Lisää tämä koodi sen runkoon:
 
* Ja viimeistellä rakentaja Create. Lisää tämä koodi sen runkoon:
<source>
+
<syntaxhighlight lang="pascal">
 
var iRow,iCol: integer;
 
var iRow,iCol: integer;
 
begin
 
begin
Line 79: Line 79:
 
     for iCol := 1 to C_MAX do
 
     for iCol := 1 to C_MAX do
 
       Cell[iRow,iCol] := ctNoAccess;
 
       Cell[iRow,iCol] := ctNoAccess;
</source>
+
</syntaxhighlight>
  
 
Nyt kun rakenteen perustiedot ovat paikallaan, on aika tarkastella mitä grafiikka tarvitsee. On monia tapoja näyttää solitaire pöytä. Tähän voidaan käyttää [[TPaintbox]]- komponenttia. Se antaa täydellisen määräysvallan graafisiin ominaisuuksiin.
 
Nyt kun rakenteen perustiedot ovat paikallaan, on aika tarkastella mitä grafiikka tarvitsee. On monia tapoja näyttää solitaire pöytä. Tähän voidaan käyttää [[TPaintbox]]- komponenttia. Se antaa täydellisen määräysvallan graafisiin ominaisuuksiin.
Line 87: Line 87:
 
* Siirry lähdekoodieditorissa ufrmmain-käännösyksikköön.
 
* Siirry lähdekoodieditorissa ufrmmain-käännösyksikköön.
 
* Lisää PegDatastructures sen uses luetteloon joka on tiedoston yläosassa:  
 
* Lisää PegDatastructures sen uses luetteloon joka on tiedoston yläosassa:  
<source>
+
<syntaxhighlight lang="pascal">
 
uses
 
uses
 
   PegDatastructures,
 
   PegDatastructures,
 
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;
 
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;
</source>
+
</syntaxhighlight>
 
* Paina {{keypress|F12}} (tämä tuo esiin lomakkeen).  
 
* Paina {{keypress|F12}} (tämä tuo esiin lomakkeen).  
 
* Komponenttipaletin [[Standard tab/fi|Standard]]-välilehdeltä valitaan [[TButton/fi|TButton]] ja pudota se lomakkeelle vasempaan yläkulmaan.
 
* Komponenttipaletin [[Standard tab/fi|Standard]]-välilehdeltä valitaan [[TButton/fi|TButton]] ja pudota se lomakkeelle vasempaan yläkulmaan.
Line 106: Line 106:
 
* Tuplaklikkaa ''Test paint''-painiketta (tämä luo tapahtumakäsittelijän).
 
* Tuplaklikkaa ''Test paint''-painiketta (tämä luo tapahtumakäsittelijän).
 
* Lisää kaksi muuttujaa:  
 
* Lisää kaksi muuttujaa:  
<source>
+
<syntaxhighlight lang="pascal">
 
var
 
var
 
   CellWidth : integer;
 
   CellWidth : integer;
 
   CellHeight: integer;
 
   CellHeight: integer;
</source>
+
</syntaxhighlight>
 
Tarvitaan ylimääräisiä muuttujia pitämään välituloksia:
 
Tarvitaan ylimääräisiä muuttujia pitämään välituloksia:
 
* Lisää kolme paikallista muuttujaa:  
 
* Lisää kolme paikallista muuttujaa:  
<source>
+
<syntaxhighlight lang="pascal">
 
   iRow, iCol: TCellNums;
 
   iRow, iCol: TCellNums;
 
   CellArea  : TRect;
 
   CellArea  : TRect;
</source>
+
</syntaxhighlight>
 
CellArea käytetään rajoittamaan suorakulmaista aluetta näytöllä, johon solu piirretään.
 
CellArea käytetään rajoittamaan suorakulmaista aluetta näytöllä, johon solu piirretään.
  
Jotta käsittellään kaikki rivit ja sarakkeet niin tehdään se kahdella suoraviivaisella silmukalla.
+
Jotta käsittellään kaikki rivit ja sarakkeet niin tehdään se kahdella suoraviivaisella [[For/fi|for]]-silmukalla.
  
 
* Lisää seuraava koodi tapahtumankäsittelijään:
 
* Lisää seuraava koodi tapahtumankäsittelijään:
<source>   
+
<syntaxhighlight lang="pascal">   
 
// Calculate the width/height of each cell to accomodate for all cells in the paintbox
 
// Calculate the width/height of each cell to accomodate for all cells in the paintbox
 
   CellWidth := pbPeg.Width div 7;
 
   CellWidth := pbPeg.Width div 7;
Line 139: Line 139:
 
       pbPeg.Canvas.Rectangle(CellArea);
 
       pbPeg.Canvas.Rectangle(CellArea);
 
     end;
 
     end;
</source>
+
</syntaxhighlight>
  
 
Canvas on nimensä mukaisesti on ohjaus, joka auttaa meitä piirtämään asioita, kuten viivoja, suorakaiteita, ympyröitä jne. TPaintbox-komponentti sisältää Canvas:n. Siksi rivillä pbPeg.Canvas.Rectangle (CellArea) ohjelma piirtää suorakulmion paintbox, rajattu alue on määritelty CellArea. Ja koska paintbox sijoitetaan lomakkeelle, voimme nähdä tuloksen siellä.
 
Canvas on nimensä mukaisesti on ohjaus, joka auttaa meitä piirtämään asioita, kuten viivoja, suorakaiteita, ympyröitä jne. TPaintbox-komponentti sisältää Canvas:n. Siksi rivillä pbPeg.Canvas.Rectangle (CellArea) ohjelma piirtää suorakulmion paintbox, rajattu alue on määritelty CellArea. Ja koska paintbox sijoitetaan lomakkeelle, voimme nähdä tuloksen siellä.
Line 153: Line 153:
 
* Lisää uusi luokka tähän käännösyksikköön, se tekee kaiken piirtämisen (luokka tulee uses-lauseen jälkeen, ennen  
 
* Lisää uusi luokka tähän käännösyksikköön, se tekee kaiken piirtämisen (luokka tulee uses-lauseen jälkeen, ennen  
 
implementation osiota).
 
implementation osiota).
<source>
+
<syntaxhighlight lang="pascal">
 
type
 
type
 
   TPegSolPainter = class
 
   TPegSolPainter = class
Line 162: Line 162:
 
     constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
 
     constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
 
   end;
 
   end;
</source>
+
</syntaxhighlight>
 
Huomaa, että TPegSolitaire muuttuja lisätään myös, koska käyttää tätä luokkaa käytetään noutamaan solujen tila.
 
Huomaa, että TPegSolitaire muuttuja lisätään myös, koska käyttää tätä luokkaa käytetään noutamaan solujen tila.
 
* Sijoita tekstikursorin rakentajan eli constructor:n riville ja paina {{keypress|Ctrl|Shift|C}}.
 
* Sijoita tekstikursorin rakentajan eli constructor:n riville ja paina {{keypress|Ctrl|Shift|C}}.
 
* Rakentajan kaksi alustus parametriä on sijoitettava luokan private muuttujiin (Täydennä luokan rakentajaa siten että se näyttää tältä) :
 
* Rakentajan kaksi alustus parametriä on sijoitettava luokan private muuttujiin (Täydennä luokan rakentajaa siten että se näyttää tältä) :
<source>
+
<syntaxhighlight lang="pascal">
 
constructor TPegSolPainter.Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
 
constructor TPegSolPainter.Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
 
begin
 
begin
Line 172: Line 172:
 
   Canvas := pCanvas;
 
   Canvas := pCanvas;
 
end;
 
end;
</source>  
+
</syntaxhighlight>  
  
 
Yritettäessä kääntää koodia niin tulee virheilmoitus koska ei olla lisätty PegDatastrucures käännösyksikköä uses-lauseseen. Ja koska käytetään myös TCanvas-luokkaa niin täytyy lisätä Graphics käännösyksikkö samoin.
 
Yritettäessä kääntää koodia niin tulee virheilmoitus koska ei olla lisätty PegDatastrucures käännösyksikköä uses-lauseseen. Ja koska käytetään myös TCanvas-luokkaa niin täytyy lisätä Graphics käännösyksikkö samoin.
 
* Lisää ''PegDatastructures'' ja ''Graphics'' ''uses'' lauseen luetteloon.  
 
* Lisää ''PegDatastructures'' ja ''Graphics'' ''uses'' lauseen luetteloon.  
<source>
+
<syntaxhighlight lang="pascal">
 
uses
 
uses
 
   PegDatastructures,
 
   PegDatastructures,
 
   Graphics,
 
   Graphics,
 
   Classes, SysUtils;  
 
   Classes, SysUtils;  
</source>
+
</syntaxhighlight>
  
 
Syy tämän luokan rakentamiseen oli poistaa kaikki piirtäminen koodilla lomakkeella. Joten on luotava menetelmä, joka tekee piirtämisen. Ennen lisäystä että menetelmä on jotain, johon on puututtava: laskea leveys solun, jaamme paintbox leveyttä useissa solujen. Teoriassa voisimme käyttää ominaisuutta Canvas.Width tähän. Kuitenkin tämä ominaisuus ei aina anna oikeata leveyttä oikeaan aikaan. Joten piirrettäesä soluja täytyy tarjota piirtää menetelmälle oikeat Canvas arvot leveydelle ja korkeudelle.
 
Syy tämän luokan rakentamiseen oli poistaa kaikki piirtäminen koodilla lomakkeella. Joten on luotava menetelmä, joka tekee piirtämisen. Ennen lisäystä että menetelmä on jotain, johon on puututtava: laskea leveys solun, jaamme paintbox leveyttä useissa solujen. Teoriassa voisimme käyttää ominaisuutta Canvas.Width tähän. Kuitenkin tämä ominaisuus ei aina anna oikeata leveyttä oikeaan aikaan. Joten piirrettäesä soluja täytyy tarjota piirtää menetelmälle oikeat Canvas arvot leveydelle ja korkeudelle.
Line 187: Line 187:
 
Nyt tiedetään tämä, voidaan lisätä piirustusmetodi luokkaan.
 
Nyt tiedetään tämä, voidaan lisätä piirustusmetodi luokkaan.
 
* Lisää aliohjelma Repaint luokkaan (jolloin luokka määrittely näyttää tältä).  
 
* Lisää aliohjelma Repaint luokkaan (jolloin luokka määrittely näyttää tältä).  
<source>   
+
<syntaxhighlight lang="pascal">   
 
TPegSolPainter = class
 
TPegSolPainter = class
 
   private
 
   private
Line 200: Line 200:
 
* Luo aliohjelman Repaint runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Luo aliohjelman Repaint runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Kopio koodi ''TfrmMain.Button1Click(Sender: TObject)''-aliohjelmasta tähän runkoon (Jolloin siitä tulee tälläinen)
 
* Kopio koodi ''TfrmMain.Button1Click(Sender: TObject)''-aliohjelmasta tähän runkoon (Jolloin siitä tulee tälläinen)
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);
 
procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);
 
var
 
var
Line 225: Line 225:
 
     end;
 
     end;
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
Koska ei enää tarvita TPaintbox-komponenttia pbPeg. Niin on poistettava viittaukset siihen. Joten kolmessa kohdassa tarvitaan muutoksia:
 
Koska ei enää tarvita TPaintbox-komponenttia pbPeg. Niin on poistettava viittaukset siihen. Joten kolmessa kohdassa tarvitaan muutoksia:
 
* Muutos CellWidth:n ja CellHeight:n laskennassa:  
 
* Muutos CellWidth:n ja CellHeight:n laskennassa:  
<source>
+
<syntaxhighlight lang="pascal">
 
   CellWidth := pCanvasWidth div 7;
 
   CellWidth := pCanvasWidth div 7;
 
   CellHeight := pCanvasHeight div 7;
 
   CellHeight := pCanvasHeight div 7;
</source>
+
</syntaxhighlight>
 
* Muutos suorakulmion piirtämisessä:
 
* Muutos suorakulmion piirtämisessä:
<source>
+
<syntaxhighlight lang="pascal">
 
   Canvas.Rectangle(CellArea);
 
   Canvas.Rectangle(CellArea);
</source>
+
</syntaxhighlight>
  
 
Nyt on luokka, jolla voidaan tehdä haluttu piirtäminen, on aika käyttää sitä. Tätä piirtoluokkaa  käytetään yhdessä PegSolitare luokan kanssa solujen piirtämiseen.
 
Nyt on luokka, jolla voidaan tehdä haluttu piirtäminen, on aika käyttää sitä. Tätä piirtoluokkaa  käytetään yhdessä PegSolitare luokan kanssa solujen piirtämiseen.
Line 243: Line 243:
 
* Poista siitä kaikki lausekkeet ja muuttujat (Tyhjennä se).
 
* Poista siitä kaikki lausekkeet ja muuttujat (Tyhjennä se).
 
* Lisätään kaksi uutta muuttujaa: peliluokan ja  piirtämisluokan (Kaikkien näiden toimien tuloksena aliohjelma nyt näyttää tältä):  
 
* Lisätään kaksi uutta muuttujaa: peliluokan ja  piirtämisluokan (Kaikkien näiden toimien tuloksena aliohjelma nyt näyttää tältä):  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.Button1Click(Sender: TObject);
 
procedure TfrmMain.Button1Click(Sender: TObject);
 
var
 
var
Line 250: Line 250:
 
begin
 
begin
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
* Lisää ''PegSolPainter'' uses-lauseen luetteloon.
 
* Lisää ''PegSolPainter'' uses-lauseen luetteloon.
  
 
Piirtoluokan käyttö on yksinkertaista: Luodaan uusi esiintymä ja kutsutaan piirto aliohjelmaa:
 
Piirtoluokan käyttö on yksinkertaista: Luodaan uusi esiintymä ja kutsutaan piirto aliohjelmaa:
 
* Lisää seuraava koodi Button1Click aliohjelmaan:  
 
* Lisää seuraava koodi Button1Click aliohjelmaan:  
<source>   
+
<syntaxhighlight lang="pascal">   
 
   // Create a new game object
 
   // Create a new game object
 
   pegsol := TPegSolitaire.Create(7);
 
   pegsol := TPegSolitaire.Create(7);
Line 268: Line 268:
 
   pegpaint.Free;
 
   pegpaint.Free;
 
   pegsol.Free
 
   pegsol.Free
</source>
+
</syntaxhighlight>
 
Suorita ja testta ohjelma. Nähdään että se toimii samaan tapaan kuin ennen. Tulos näyttää tältä:
 
Suorita ja testta ohjelma. Nähdään että se toimii samaan tapaan kuin ennen. Tulos näyttää tältä:
 
[[Image:tutpeg_empty_cells.png]]
 
[[Image:tutpeg_empty_cells.png]]
Line 277: Line 277:
  
 
Kuten edellisessä osassa on nähty, solun matriisi joka tehtiin hävisi jos lomakkeen kokoa muutettiin. Näin tapahtuu, koska lomake ei tiedä mitään "pikku pelistä". Heti kun lomakkeen mielestä on aika piirtää itse, se tekee niin ja ohittaa "pikku pelin". Mitä se tekee? Se vain lähettää viestin lapsi kontrolleille, että virkistäminen on välttämätön. Tämä viesti on käytettävissämme tapahtumana: OnPaint-tapahtuma.
 
Kuten edellisessä osassa on nähty, solun matriisi joka tehtiin hävisi jos lomakkeen kokoa muutettiin. Näin tapahtuu, koska lomake ei tiedä mitään "pikku pelistä". Heti kun lomakkeen mielestä on aika piirtää itse, se tekee niin ja ohittaa "pikku pelin". Mitä se tekee? Se vain lähettää viestin lapsi kontrolleille, että virkistäminen on välttämätön. Tämä viesti on käytettävissämme tapahtumana: OnPaint-tapahtuma.
* Mene lomakkeelle (Valitse ufrmMain lähdekoodieditorissa ja paina F12).
+
* Mene lomakkeelle (Valitse ufrmMain lähdekoodieditorissa ja paina {{keypress|F12}}).
 
* Valitse TPaintbox pbPeg.
 
* Valitse TPaintbox pbPeg.
 
* Siirry ''tapahtumat''-välilehdelle komponenttimuokkaimessa.
 
* Siirry ''tapahtumat''-välilehdelle komponenttimuokkaimessa.
Line 287: Line 287:
 
* Valitse OnPaint tapahtuma komponenttimuokkaimessa ja klikkaa pientä (...) painiketta kolmella pisteellä . Tämä luo tapahtumakäsittelijän rungon.
 
* Valitse OnPaint tapahtuma komponenttimuokkaimessa ja klikkaa pientä (...) painiketta kolmella pisteellä . Tämä luo tapahtumakäsittelijän rungon.
 
* Kopioi / Liitä tarkka koodi ''Button1Click'' tapahtumakäsittelystä tähän uuteen aliohjelmaan.  
 
* Kopioi / Liitä tarkka koodi ''Button1Click'' tapahtumakäsittelystä tähän uuteen aliohjelmaan.  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.pbPegPaint(Sender: TObject);
 
procedure TfrmMain.pbPegPaint(Sender: TObject);
 
var
 
var
Line 306: Line 306:
 
   pegsol.Free
 
   pegsol.Free
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
* Poista kaikki koodi ja muuttujat aliohjelmasta Button1Click. Muista: Ohjelmankehitysympäristö automaattisesti poistaa tämän tyhjän aliohjelman.
 
* Poista kaikki koodi ja muuttujat aliohjelmasta Button1Click. Muista: Ohjelmankehitysympäristö automaattisesti poistaa tämän tyhjän aliohjelman.
 
* Poista ''Test paint'' -painike lomakkeelta.
 
* Poista ''Test paint'' -painike lomakkeelta.
Line 315: Line 315:
 
* Siirry lomakkeen määrittelyyn.
 
* Siirry lomakkeen määrittelyyn.
 
* Lisää kaksi muuttujaa jotka luotiin OnPaint tapahtumaan:  
 
* Lisää kaksi muuttujaa jotka luotiin OnPaint tapahtumaan:  
<source>
+
<syntaxhighlight lang="pascal">
 
   TfrmMain = class(TForm)
 
   TfrmMain = class(TForm)
 
     pbPeg: TPaintBox;
 
     pbPeg: TPaintBox;
Line 326: Line 326:
 
     { public declarations }
 
     { public declarations }
 
   end;  
 
   end;  
</source>
+
</syntaxhighlight>
  
  
Line 332: Line 332:
 
Nämä muuttujat pitää alustaa heti, kun lomake avataan (tai kun halutaan aloittaa uusi peli). Joten luodaan aliohjelma, joka tekee sen meille ja lisätään se private-osioon lomakkeelle.
 
Nämä muuttujat pitää alustaa heti, kun lomake avataan (tai kun halutaan aloittaa uusi peli). Joten luodaan aliohjelma, joka tekee sen meille ja lisätään se private-osioon lomakkeelle.
 
* Lisää aliohjelma StartNewGame lomakkeelle.  
 
* Lisää aliohjelma StartNewGame lomakkeelle.  
<source>   
+
<syntaxhighlight lang="pascal">   
 
   private
 
   private
 
     { private declarations }
 
     { private declarations }
Line 339: Line 339:
  
 
     procedure StartNewGame;
 
     procedure StartNewGame;
</source>
+
</syntaxhighlight>
 
* Luo aliohjelman runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Luo aliohjelman runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Lisää alustus koodi:
 
* Lisää alustus koodi:
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.StartNewGame;
 
procedure TfrmMain.StartNewGame;
 
begin
 
begin
Line 353: Line 353:
 
   pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);
 
   pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
Nyt kun alustuksen koodi on luotu, se on suoritettava. Looginen aika tehdä tämä on, kun lomake on luodaan (eli sovellus käynnistetään). Tähän toiseen tapahtumaan pääse kiinni: FormCreate-tapahtumassa. Se voidaan luoda kahdella eri tavalla: Komponenttimuokkaimessa löytää OnFormCreate tapahtuma ja klikata "..." -painiketta. Toinen tapa tuottaa se on tuplaklikata lomaketta.
 
Nyt kun alustuksen koodi on luotu, se on suoritettava. Looginen aika tehdä tämä on, kun lomake on luodaan (eli sovellus käynnistetään). Tähän toiseen tapahtumaan pääse kiinni: FormCreate-tapahtumassa. Se voidaan luoda kahdella eri tavalla: Komponenttimuokkaimessa löytää OnFormCreate tapahtuma ja klikata "..." -painiketta. Toinen tapa tuottaa se on tuplaklikata lomaketta.
* Mene lomakkeelle (Lähdekoodieditorissa paina F12).
+
* Mene lomakkeelle (Lähdekoodieditorissa paina {{keypress|F12}}).
 
* Tuplaklikkaa jossain vapaata aluetta. Älä klikkaa TPaintbox:a.  
 
* Tuplaklikkaa jossain vapaata aluetta. Älä klikkaa TPaintbox:a.  
 
[[Image:tutpeg_form_create.png]]
 
[[Image:tutpeg_form_create.png]]
 
* Lisää sen runkoon aliohjelman StartNewGame kutsu:
 
* Lisää sen runkoon aliohjelman StartNewGame kutsu:
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.FormCreate(Sender: TObject);
 
procedure TfrmMain.FormCreate(Sender: TObject);
 
begin
 
begin
 
   StartNewGame;
 
   StartNewGame;
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
Nyt kun peli- ja piirto-oliot luodaan ohjelman alkaessa niitä ei enää tarvita OnPaint aliohjelmassa.
 
Nyt kun peli- ja piirto-oliot luodaan ohjelman alkaessa niitä ei enää tarvita OnPaint aliohjelmassa.
 
* Paikallista aliohjelma procedure TfrmMain.pbPegPaint (Sender: TObject);
 
* Paikallista aliohjelma procedure TfrmMain.pbPegPaint (Sender: TObject);
 
* Poista paikalliset muuttujat ja kaikki koodi paitsi rivin joka tekee piirtämisen. (jolloin se on näin yksinkertainen)
 
* Poista paikalliset muuttujat ja kaikki koodi paitsi rivin joka tekee piirtämisen. (jolloin se on näin yksinkertainen)
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.pbPegPaint(Sender: TObject);
 
procedure TfrmMain.pbPegPaint(Sender: TObject);
 
begin
 
begin
Line 376: Line 376:
 
   pegpaint.Repaint(pbPeg.Width, pbPeg.Height);
 
   pegpaint.Repaint(pbPeg.Width, pbPeg.Height);
 
end;
 
end;
</source>  
+
</syntaxhighlight>  
 
* Suorita ohjelma ja katso mitä tapahtuu.
 
* Suorita ohjelma ja katso mitä tapahtuu.
  
Line 393: Line 393:
 
* Paikanna aliohjelma ''TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);''.
 
* Paikanna aliohjelma ''TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);''.
 
Siellä nähdään numero 7 muutamia kertoja.  Tämä on se maaginen numero, joka tekee temppuja. Laskettaessa solun leveys ja korkeus, tarvitsemme laudalle koon joka on tallennettu pegsol pelin Size muuttujaan. Ja sama pätee iRow ja iCol silmukoihin. Joten korjataan ne kerralla:
 
Siellä nähdään numero 7 muutamia kertoja.  Tämä on se maaginen numero, joka tekee temppuja. Laskettaessa solun leveys ja korkeus, tarvitsemme laudalle koon joka on tallennettu pegsol pelin Size muuttujaan. Ja sama pätee iRow ja iCol silmukoihin. Joten korjataan ne kerralla:
<source>   
+
<syntaxhighlight lang="pascal">   
 
// Calculate the width of each cell to accomodate for all cells
 
// Calculate the width of each cell to accomodate for all cells
 
   CellWidth := pCanvasWidth div pegsol.Size;
 
   CellWidth := pCanvasWidth div pegsol.Size;
Line 410: Line 410:
 
       Canvas.Rectangle(CellArea);
 
       Canvas.Rectangle(CellArea);
 
     end;
 
     end;
</source>
+
</syntaxhighlight>
  
 
Tässä varoitus: pegsol:ssa ei ole julkisesti saatavilla Size -muuttujaa. Ja näin se pitäisi olla: kaikki luokan muuttujat  pitäisi olla yksityisiä. Tapa käyttää näitä yksityisiä arvoja on funktioiden tai ominaisuuksien (rajapinta luokan) kautta. Tähän yksinkertaiseen arvoon sovelletaan ominaisuutta.
 
Tässä varoitus: pegsol:ssa ei ole julkisesti saatavilla Size -muuttujaa. Ja näin se pitäisi olla: kaikki luokan muuttujat  pitäisi olla yksityisiä. Tapa käyttää näitä yksityisiä arvoja on funktioiden tai ominaisuuksien (rajapinta luokan) kautta. Tähän yksinkertaiseen arvoon sovelletaan ominaisuutta.
Line 419: Line 419:
 
Luokka nyt näyttää tältä:  
 
Luokka nyt näyttää tältä:  
  
<source>   
+
<syntaxhighlight lang="pascal">   
 
TPegSolitaire = class
 
TPegSolitaire = class
 
   private
 
   private
Line 432: Line 432:
 
     property Size: TCellNums read FSize;
 
     property Size: TCellNums read FSize;
 
   end;
 
   end;
</source>
+
</syntaxhighlight>
 
On yleinen käytäntö  käyttää muuttujien etuliitteenä kirjainta ''F'' joihin päästään käsiksi ominaisuuksilla. Julkinen pääsy ominaisuuteen Size on vain sen arvon lukeminen koska sitä ei pidä koskaan muuttaa kun peli käynnistetään. Ainoa paikka, jossa tämä yksityinen muuttuja pitäisi saada sen lopullinen arvon on Create rakentaja.
 
On yleinen käytäntö  käyttää muuttujien etuliitteenä kirjainta ''F'' joihin päästään käsiksi ominaisuuksilla. Julkinen pääsy ominaisuuteen Size on vain sen arvon lukeminen koska sitä ei pidä koskaan muuttaa kun peli käynnistetään. Ainoa paikka, jossa tämä yksityinen muuttuja pitäisi saada sen lopullinen arvon on Create rakentaja.
 
* Paikallista rakentaja (constructor).
 
* Paikallista rakentaja (constructor).
 
* Muuta rivi''Size := pSize;'' riviksi ''FSize := pSize;'' (Jotta ei saada käännösvirhettä).
 
* Muuta rivi''Size := pSize;'' riviksi ''FSize := pSize;'' (Jotta ei saada käännösvirhettä).
 
* Ja kun ollaan siellä niin alustusta tarvitsee vähäisen korjata. Ei tarvitse alustaa soluja joita ei aiota käyttää. Joten rakentaja tulisi näyttää tältä (''C_MAX''  korvataan ''Size'':lla):  
 
* Ja kun ollaan siellä niin alustusta tarvitsee vähäisen korjata. Ei tarvitse alustaa soluja joita ei aiota käyttää. Joten rakentaja tulisi näyttää tältä (''C_MAX''  korvataan ''Size'':lla):  
<source>
+
<syntaxhighlight lang="pascal">
 
constructor TPegSolitaire.Create(const pSize: TCellNums);
 
constructor TPegSolitaire.Create(const pSize: TCellNums);
 
var iRow,iCol: integer;
 
var iRow,iCol: integer;
Line 446: Line 446:
 
       Cell[iRow,iCol] := ctNoAccess;
 
       Cell[iRow,iCol] := ctNoAccess;
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
Olemme nyt valmis testaamaan ohjelman ja katso jos se piirtää nyt mukavan 5x5 matriisi.
 
Olemme nyt valmis testaamaan ohjelman ja katso jos se piirtää nyt mukavan 5x5 matriisi.
Line 463: Line 463:
 
Täällä täytyy tehdä muutoksia. Se riippuu solun tilasta mitä tarvitaan piirtämiseen (ei käytettävissä oleva solu, tyhjä solu tai pelinappula solu). Tapaus ... toiminta tulee pelastus.
 
Täällä täytyy tehdä muutoksia. Se riippuu solun tilasta mitä tarvitaan piirtämiseen (ei käytettävissä oleva solu, tyhjä solu tai pelinappula solu). Tapaus ... toiminta tulee pelastus.
 
* Muuta solujen piirtämistä:
 
* Muuta solujen piirtämistä:
<source>
+
<syntaxhighlight lang="pascal">
 
   // Draw boxes for all cells
 
   // Draw boxes for all cells
 
   for iRow := 1 to pegsol.Size do
 
   for iRow := 1 to pegsol.Size do
Line 498: Line 498:
 
       end;
 
       end;
 
     end;
 
     end;
</source>
+
</syntaxhighlight>
  
 
Ohjelmaa voitaisiin ajaa tässä vaiheessa (tai vain kokeilla sitä), mutta se on vain tylsä? (5x5) harmaa ruudukko. Tämä johtuu siitä ettei vielä ole määritelty mitään peliasetuksia. Korjataan sitä seuraavaksi.
 
Ohjelmaa voitaisiin ajaa tässä vaiheessa (tai vain kokeilla sitä), mutta se on vain tylsä? (5x5) harmaa ruudukko. Tämä johtuu siitä ettei vielä ole määritelty mitään peliasetuksia. Korjataan sitä seuraavaksi.
Line 505: Line 505:
 
* Luo 5x5 pelin sijasta 7x7 peli.
 
* Luo 5x5 pelin sijasta 7x7 peli.
 
* Alusta muutamia soluja. Esimerkiksi:  
 
* Alusta muutamia soluja. Esimerkiksi:  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.StartNewGame;
 
procedure TfrmMain.StartNewGame;
 
begin
 
begin
Line 525: Line 525:
 
   pegsol.Cell[5,4] := ctEmpty;
 
   pegsol.Cell[5,4] := ctEmpty;
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
* Suorita ohjelma (sen tulee näyttää jokseenkin samanlaiselta kuin kuva alla).
 
* Suorita ohjelma (sen tulee näyttää jokseenkin samanlaiselta kuin kuva alla).
 
[[Image:tutpeg_first_pegs.png]]
 
[[Image:tutpeg_first_pegs.png]]
Line 536: Line 536:
  
 
Jos täytetään kaikki solut yksitellen, niin se johtaisi suureen koodimäärään. Mitä jos voitaisiin alustaa peli vain  tekstillä, joka symbolisesti kuvaa pelilaudan? Jotain tällaista:
 
Jos täytetään kaikki solut yksitellen, niin se johtaisi suureen koodimäärään. Mitä jos voitaisiin alustaa peli vain  tekstillä, joka symbolisesti kuvaa pelilaudan? Jotain tällaista:
<source>   
+
<syntaxhighlight lang="pascal">   
 
// Initialize the cells to the classic game
 
// Initialize the cells to the classic game
 
   pegsol.InitializeBoard( '  ooo  ' + LineEnding +
 
   pegsol.InitializeBoard( '  ooo  ' + LineEnding +
Line 545: Line 545:
 
                           '  ooo  ' + LineEnding +
 
                           '  ooo  ' + LineEnding +
 
                           '  ooo  ' );
 
                           '  ooo  ' );
</source>
+
</syntaxhighlight>
 
#'''o''' on solu jossa on pelinappula.
 
#'''o''' on solu jossa on pelinappula.
 
#'''.''' on tyhjä, mutta pelattavaa solu.
 
#'''.''' on tyhjä, mutta pelattavaa solu.
Line 553: Line 553:
  
 
* Ollaan optimistinen (kutsutaan myös ''Top down'' suunnitteluksi) ja lisätään edellä ollut koodi TfrmMain.StartNewGame:  
 
* Ollaan optimistinen (kutsutaan myös ''Top down'' suunnitteluksi) ja lisätään edellä ollut koodi TfrmMain.StartNewGame:  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.StartNewGame;
 
procedure TfrmMain.StartNewGame;
 
begin
 
begin
Line 573: Line 573:
 
                           '  ooo  ' );
 
                           '  ooo  ' );
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
* Avaa PegDatastructures lähdekooditiedosto.
 
* Avaa PegDatastructures lähdekooditiedosto.
 
* Lisää tämä aliohjelman esittely luokan public osioon:  ''InitializeBoard(const pBoard: ansistring);''
 
* Lisää tämä aliohjelman esittely luokan public osioon:  ''InitializeBoard(const pBoard: ansistring);''
<source>   
+
<syntaxhighlight lang="pascal">   
 
public
 
public
 
     constructor Create(const pSize: TCellNums);
 
     constructor Create(const pSize: TCellNums);
Line 583: Line 583:
 
     property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;
 
     property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;
 
     property Size: TCellNums read FSize;
 
     property Size: TCellNums read FSize;
</source>
+
</syntaxhighlight>
 
* Luo aliohjelman InitializeBoard:n runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Luo aliohjelman InitializeBoard:n runko. Paina {{keypress|Ctrl|Shift|C}}.
 
Mitä tämä aliohjelman tarvitse tehdä? Se jakaa textstringin osiksi erillisiin riveihin ja sitten käsitellä nämä rivit, koska käytettiin LineEnding-vakiota erottamaan rivit niin voidaan käyttää ''TStringList'' luokkaa erottamaan ne.
 
Mitä tämä aliohjelman tarvitse tehdä? Se jakaa textstringin osiksi erillisiin riveihin ja sitten käsitellä nämä rivit, koska käytettiin LineEnding-vakiota erottamaan rivit niin voidaan käyttää ''TStringList'' luokkaa erottamaan ne.
  
 
* Lisää ''InitalizeBoard''  koodi:
 
* Lisää ''InitalizeBoard''  koodi:
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);
 
procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);
 
var lst      : TStringList;
 
var lst      : TStringList;
Line 617: Line 617:
 
   lst.Free;
 
   lst.Free;
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
# TStringList käytetään puskurina. Tämä toimii, koska  ''LineEnding'':a käytettiin rivien erottimena.
 
# TStringList käytetään puskurina. Tämä toimii, koska  ''LineEnding'':a käytettiin rivien erottimena.
 
#  ''Count'' kertoo rivien määrän, mutta ne on numeroitu 0..Count-1. Ja solut on numeroitu aloittaen ykkösestä(1). Siksi on solua osoittaessa tehtävä ''iRow + 1''.  
 
#  ''Count'' kertoo rivien määrän, mutta ne on numeroitu 0..Count-1. Ja solut on numeroitu aloittaen ykkösestä(1). Siksi on solua osoittaessa tehtävä ''iRow + 1''.  
  
 
Edellä esitetty menettely sisältää paljon ylimääräisiä muuttujia ja ei pitäisi olla kovin vaikea ymmärtää. On mahdollista vähentää aliohjelma minimiin kuten näin:
 
Edellä esitetty menettely sisältää paljon ylimääräisiä muuttujia ja ei pitäisi olla kovin vaikea ymmärtää. On mahdollista vähentää aliohjelma minimiin kuten näin:
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);
 
procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);
 
var iRow,iCol: integer;
 
var iRow,iCol: integer;
Line 639: Line 639:
 
   end;
 
   end;
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
Tämä menettely tekee täsmälleen saman. Se käyttää kätevää ominaisuutta, sitä että voidaan käyttää With-lausetta  kun luodaan dynaamisesti oliota. Toimiakseen tämä aliohjelma vaatii että lisätään ''Math'' käännösyksikkö uses lauseen luetteloon.
 
Tämä menettely tekee täsmälleen saman. Se käyttää kätevää ominaisuutta, sitä että voidaan käyttää With-lausetta  kun luodaan dynaamisesti oliota. Toimiakseen tämä aliohjelma vaatii että lisätään ''Math'' käännösyksikkö uses lauseen luetteloon.
  
Line 662: Line 662:
 
Syntyvään aliohjelman otsikkoon sisältyy useita muuttujia. Kaksi niistä ovat X ja Y. Ne sisältävät TPaintBox:n paikan jossa hiirtä painettiin. Tehdään ne näkyviksi lisäämällä joitakin tietoja TMemo-komponenttiin joka on lisätty lomakkeelle.
 
Syntyvään aliohjelman otsikkoon sisältyy useita muuttujia. Kaksi niistä ovat X ja Y. Ne sisältävät TPaintBox:n paikan jossa hiirtä painettiin. Tehdään ne näkyviksi lisäämällä joitakin tietoja TMemo-komponenttiin joka on lisätty lomakkeelle.
 
* Lisää seuraavat lauseet aliohjelmaan; se lisää muotoillun rivin TMemo-komponentin näyttämään paikkaa, missä hiirtä painettiin:  
 
* Lisää seuraavat lauseet aliohjelmaan; se lisää muotoillun rivin TMemo-komponentin näyttämään paikkaa, missä hiirtä painettiin:  
<source>
+
<syntaxhighlight lang="pascal">
 
Memo1.Append( format('Mouse down @ %dx%d',[X,Y]) );
 
Memo1.Append( format('Mouse down @ %dx%d',[X,Y]) );
</source>
+
</syntaxhighlight>
 
* Suorita ohjelma. (Paina {{keypress|F9}}).
 
* Suorita ohjelma. (Paina {{keypress|F9}}).
 
* Klikkaa hiirellä pelilautaa useissa eri paikoissa. Muistion (TMemon) ruutu näyttää missä hiiri painettiin.
 
* Klikkaa hiirellä pelilautaa useissa eri paikoissa. Muistion (TMemon) ruutu näyttää missä hiiri painettiin.
Line 671: Line 671:
 
* Avaa PegDatastructures käännösyksikkö.
 
* Avaa PegDatastructures käännösyksikkö.
 
* Lisää ''TCellPosition'' tietuetyyppi johon tallennetaan solun koordinaatit, tyyppien esittelyosioon:  
 
* Lisää ''TCellPosition'' tietuetyyppi johon tallennetaan solun koordinaatit, tyyppien esittelyosioon:  
<source>
+
<syntaxhighlight lang="pascal">
 
type
 
type
 
   TCellNums = 1..C_MAX;
 
   TCellNums = 1..C_MAX;
Line 681: Line 681:
 
     Col: TCellNums;
 
     Col: TCellNums;
 
   end;
 
   end;
</source>
+
</syntaxhighlight>
 
* Avaa PegSolPainter käännösyksikkö.
 
* Avaa PegSolPainter käännösyksikkö.
 
* Lisää luokan julkiseen (public)osioon funktio CanvasXYtoCell (...):  
 
* Lisää luokan julkiseen (public)osioon funktio CanvasXYtoCell (...):  
<source>
+
<syntaxhighlight lang="pascal">
 
   public
 
   public
 
     constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
 
     constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
Line 690: Line 690:
 
     procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);
 
     procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);
 
     function CanvasXYtoCell(const pX, pY: integer): TCellPosition;
 
     function CanvasXYtoCell(const pX, pY: integer): TCellPosition;
</source>
+
</syntaxhighlight>
 
* Luo funktion runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Luo funktion runko. Paina {{keypress|Ctrl|Shift|C}}.
 
* Lisää seuraava koodi:
 
* Lisää seuraava koodi:
<source>
+
<syntaxhighlight lang="pascal">
 
function TPegSolPainter.CanvasXYtoCell(const pX, pY: integer): TCellPosition;
 
function TPegSolPainter.CanvasXYtoCell(const pX, pY: integer): TCellPosition;
 
begin
 
begin
Line 699: Line 699:
 
   result.Row := (pY div CellHeight) + 1;
 
   result.Row := (pY div CellHeight) + 1;
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
Edellä koodi kartoittaa suuret X / Y-koordinaatit vastaamaan solun rivi ja sarake numeroita. Yksi ongelma on kuitenkin: Täällä CellWidth ja CellHeight ei ole saatavilla. Ne ovat välituloksia Repaint aliohjelman eikä niitä tallenneteta. Tämä pitää korjata.
 
Edellä koodi kartoittaa suuret X / Y-koordinaatit vastaamaan solun rivi ja sarake numeroita. Yksi ongelma on kuitenkin: Täällä CellWidth ja CellHeight ei ole saatavilla. Ne ovat välituloksia Repaint aliohjelman eikä niitä tallenneteta. Tämä pitää korjata.
 
* Lisää ''CellWidth'' ja ''CellHeight''  yksityisinä muuttujina ''TPegSolPainter'' luokkaan:  
 
* Lisää ''CellWidth'' ja ''CellHeight''  yksityisinä muuttujina ''TPegSolPainter'' luokkaan:  
<source>
+
<syntaxhighlight lang="pascal">
 
   TPegSolPainter = class
 
   TPegSolPainter = class
 
   private
 
   private
Line 709: Line 709:
 
     CellWidth  : integer;
 
     CellWidth  : integer;
 
     CellHeight  : integer;
 
     CellHeight  : integer;
</source>
+
</syntaxhighlight>
 
* Poista muuttujat ''CellWidth'' and ''CellHeight''  aliohjelman Repaint muuttujien esittelystä:
 
* Poista muuttujat ''CellWidth'' and ''CellHeight''  aliohjelman Repaint muuttujien esittelystä:
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);
 
procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);
 
var
 
var
Line 717: Line 717:
 
   CellArea    : TRect;
 
   CellArea    : TRect;
 
begin
 
begin
</source>
+
</syntaxhighlight>
 
Nyt voidaan muuttaa aliohjelmaa, missä hiiren napin alaspainamisen koordinaattia käsiteltiin. Uuden funktion X / Y-paikka voidaan nyt sovittaa solun koordinaatteihin.
 
Nyt voidaan muuttaa aliohjelmaa, missä hiiren napin alaspainamisen koordinaattia käsiteltiin. Uuden funktion X / Y-paikka voidaan nyt sovittaa solun koordinaatteihin.
 
* Avaa ufrmmain käännösyksikkö.
 
* Avaa ufrmmain käännösyksikkö.
 
* Paikallista ''pbPegMouseDown'' aliohjelma.
 
* Paikallista ''pbPegMouseDown'' aliohjelma.
 
* Muuta koodi:  
 
* Muuta koodi:  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
var CellRC: TCellPosition;
 
var CellRC: TCellPosition;
Line 729: Line 729:
 
   Memo1.Append( format('Mouse down @ %dx%d',[CellRC.Row, CellRC.Col]) );
 
   Memo1.Append( format('Mouse down @ %dx%d',[CellRC.Row, CellRC.Col]) );
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
Tehdään sama OnMouseUp tapahtumaan:
 
Tehdään sama OnMouseUp tapahtumaan:
 
* Luo aliohjelman runko OnMouseUp tapahtumaan.
 
* Luo aliohjelman runko OnMouseUp tapahtumaan.
 
* Lisää tämä koodi:  
 
* Lisää tämä koodi:  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
begin
 
begin
Line 739: Line 739:
 
     Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) );
 
     Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) );
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
Huomaa, että tämä menetelmä tekee täsmälleen sama kuin OnMouseDown, mutta käyttäen ''with''-rakennetta säästetään vaivaa määrittää paikallinen muuttuja (vähemmän on enemmän ...).
 
Huomaa, että tämä menetelmä tekee täsmälleen sama kuin OnMouseDown, mutta käyttäen ''with''-rakennetta säästetään vaivaa määrittää paikallinen muuttuja (vähemmän on enemmän ...).
  
Line 759: Line 759:
 
* Avaa ufrmmain käännösyksikkö.
 
* Avaa ufrmmain käännösyksikkö.
 
* Lisää muuttuja ''FromCell'' lomakkeen yksityiseksi muuttujaksi:  
 
* Lisää muuttuja ''FromCell'' lomakkeen yksityiseksi muuttujaksi:  
<source>   
+
<syntaxhighlight lang="pascal">   
 
private
 
private
 
     { private declarations }
 
     { private declarations }
Line 765: Line 765:
 
     pegpaint: TPegSolPainter; // The paint class for the game
 
     pegpaint: TPegSolPainter; // The paint class for the game
 
     FromCell: TCellPosition;  // The peg that is going to leap
 
     FromCell: TCellPosition;  // The peg that is going to leap
</source>
+
</syntaxhighlight>
 
* Mene OnMouseDown tapahtumankäsittelijään.
 
* Mene OnMouseDown tapahtumankäsittelijään.
 
* Lisää seuraava rivi tähän aliohjelmaan:  
 
* Lisää seuraava rivi tähän aliohjelmaan:  
<source>
+
<syntaxhighlight lang="pascal">
 
   FromCell := pegpaint.CanvasXYtoCell(X,Y);
 
   FromCell := pegpaint.CanvasXYtoCell(X,Y);
</source>
+
</syntaxhighlight>
 
Nyt kun käyttäjä vapauttaa hiiren painikkeen, on suoritettava hyppy (myös tarkistettava, onko se ok ja päivittää pelilautaa). Ollaan optimistisia (muista Top Down huomautus?) Ja olettaa, on aliohjelma, joka tekee sen meille: Leap (<FromCell>, <ToCell>).
 
Nyt kun käyttäjä vapauttaa hiiren painikkeen, on suoritettava hyppy (myös tarkistettava, onko se ok ja päivittää pelilautaa). Ollaan optimistisia (muista Top Down huomautus?) Ja olettaa, on aliohjelma, joka tekee sen meille: Leap (<FromCell>, <ToCell>).
 
* Paikallista OnMouseUp tapahtumakäsittelijä ja lisää seuraava rivi:  
 
* Paikallista OnMouseUp tapahtumakäsittelijä ja lisää seuraava rivi:  
<source>
+
<syntaxhighlight lang="pascal">
 
   pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));
 
   pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));
</source>
+
</syntaxhighlight>
 
''FromCell'' oli jo pelinappula kun hiiren näppäintä painettiin. Kohdesolun lasketaan yksinkertaisesti kutsumalla pegpaint.CanvasXYToCell (...). Voisimme ottaa käyttöön paikallinen muuttuja sitä, mutta se ei ole välttämättä tarpeen.
 
''FromCell'' oli jo pelinappula kun hiiren näppäintä painettiin. Kohdesolun lasketaan yksinkertaisesti kutsumalla pegpaint.CanvasXYToCell (...). Voisimme ottaa käyttöön paikallinen muuttuja sitä, mutta se ei ole välttämättä tarpeen.
 
* Lisää seuraava rivi aliohjelmaan:
 
* Lisää seuraava rivi aliohjelmaan:
<source>
+
<syntaxhighlight lang="pascal">
 
   pbPeg.Repaint;
 
   pbPeg.Repaint;
</source>  
+
</syntaxhighlight>  
 
Tämä pakottaa paintbox:n piirtämään itsensä, joten kaikki päivitetyt solut näyttävät oikean sisällön.
 
Tämä pakottaa paintbox:n piirtämään itsensä, joten kaikki päivitetyt solut näyttävät oikean sisällön.
  
 
Täydellinen OnMouseDown aliohjelma nyt näyttää tältä:  
 
Täydellinen OnMouseDown aliohjelma nyt näyttää tältä:  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
begin
 
begin
Line 792: Line 792:
 
   pbPeg.Repaint;
 
   pbPeg.Repaint;
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
Ennenkuin mennään Leap aliohjelmaan niin vähän lisätoimintoja otetaan käyttöön. Muista, että voidaan noutaa solun tila (tyhjä, pelinappula tai ei pääsyä) TPegSolitaire:n Cell ominaisuuden kautta! Esimerkiksi pegsol.Cell [2,2] antaisi  rivin 2 ja sarakkeen 2 solun tilan. Mutta tähän väliin, on otettu käyttöön uusi tietorakenne: TCellPosition. Niinpä esimerkiksi, jos halutaan saada tila ''FromCell'':stä tarvitaan seuraava hankala lause:
 
Ennenkuin mennään Leap aliohjelmaan niin vähän lisätoimintoja otetaan käyttöön. Muista, että voidaan noutaa solun tila (tyhjä, pelinappula tai ei pääsyä) TPegSolitaire:n Cell ominaisuuden kautta! Esimerkiksi pegsol.Cell [2,2] antaisi  rivin 2 ja sarakkeen 2 solun tilan. Mutta tähän väliin, on otettu käyttöön uusi tietorakenne: TCellPosition. Niinpä esimerkiksi, jos halutaan saada tila ''FromCell'':stä tarvitaan seuraava hankala lause:
<source>
+
<syntaxhighlight lang="pascal">
 
   state := pegsol.Cell[ FromCell.Row, FramCell.Col ]
 
   state := pegsol.Cell[ FromCell.Row, FramCell.Col ]
</source>
+
</syntaxhighlight>
 
Olisi mukavaa, jos voisi kirjoittaa jotain:
 
Olisi mukavaa, jos voisi kirjoittaa jotain:
<source>
+
<syntaxhighlight lang="pascal">
 
   state := pegsol.GetCell(FromCell)
 
   state := pegsol.GetCell(FromCell)
</source>
+
</syntaxhighlight>
 
Lisätään tämä peli luokkaan.
 
Lisätään tämä peli luokkaan.
 
* Avaa käännösyksikkö PegDatastructures.
 
* Avaa käännösyksikkö PegDatastructures.
 
* Lisää yksityinen (private) funktio:''GetCell (const pPosition: TCellPosition): TCellType;''  
 
* Lisää yksityinen (private) funktio:''GetCell (const pPosition: TCellPosition): TCellType;''  
<source>
+
<syntaxhighlight lang="pascal">
 
   TPegSolitaire = class
 
   TPegSolitaire = class
 
   private
 
   private
Line 812: Line 812:
 
     procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);
 
     procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);
 
     function GetCell(const pPosition: TCellPosition): TCellType;
 
     function GetCell(const pPosition: TCellPosition): TCellType;
</source>
+
</syntaxhighlight>
 
Nyt on kaksi samannimistä GetCell funktiota, mutta niiden parametriluettelo on erilainen. Joten Lazarus tietää tarkalleen, milloin kutsua kyseistä funktiota.
 
Nyt on kaksi samannimistä GetCell funktiota, mutta niiden parametriluettelo on erilainen. Joten Lazarus tietää tarkalleen, milloin kutsua kyseistä funktiota.
 
* Luo funktion runko (Paina {{keypress|Ctrl|Shift|C}}).
 
* Luo funktion runko (Paina {{keypress|Ctrl|Shift|C}}).
 
* Lisätään seuraava koodi:  
 
* Lisätään seuraava koodi:  
<source>
+
<syntaxhighlight lang="pascal">
 
   result := Cell[pPosition.Row, pPosition.Col];
 
   result := Cell[pPosition.Row, pPosition.Col];
</source>
+
</syntaxhighlight>
  
 
Takaisin "pääjuoneen". On otettu käyttöön aliohjelma ''Leap (...)'', mutta sitä ei vielä ole. Se on aliohjelma, joka muuttaa peliä pelilaudalla, joten paikka mihin laittaa se on TPegSolitaire luokka.
 
Takaisin "pääjuoneen". On otettu käyttöön aliohjelma ''Leap (...)'', mutta sitä ei vielä ole. Se on aliohjelma, joka muuttaa peliä pelilaudalla, joten paikka mihin laittaa se on TPegSolitaire luokka.
 
* Avaa käännösyksikkö PegDatastructures.
 
* Avaa käännösyksikkö PegDatastructures.
 
* Lisää aliohjelma Leap luokan TPegSolitaire public osioon.  
 
* Lisää aliohjelma Leap luokan TPegSolitaire public osioon.  
<source>
+
<syntaxhighlight lang="pascal">
 
   public
 
   public
 
     constructor Create(const pSize: TCellNums);
 
     constructor Create(const pSize: TCellNums);
 
     procedure InitializeBoard(const pBoard: ansistring);
 
     procedure InitializeBoard(const pBoard: ansistring);
 
     procedure Leap(const pFromCell, pToCell: TCellPosition);
 
     procedure Leap(const pFromCell, pToCell: TCellPosition);
</source>
+
</syntaxhighlight>
 
* Luo aliohjelman runko (Paina {{keypress|Ctrl|Shift|C}}).
 
* Luo aliohjelman runko (Paina {{keypress|Ctrl|Shift|C}}).
  
 
Nyt tämä aliohjelma tekee kaiken kovan työn: tarkistaa, että solut ovat voimassa ja onko hyppy on suinkin mahdollista. Tämä ei ole vaikeaa, mutta tarkastusten määrä on varsin laaja jotain, joka näyttää yksinkertaista. Koodi on melko itsestään selvä, ja jos ei, toivottavasti kommentit selittää mitä tapahtuu. Huomaa, että aliohjelma käyttää GetCell (<cellposition>) funktiota joka luotiin aikaisemmin.
 
Nyt tämä aliohjelma tekee kaiken kovan työn: tarkistaa, että solut ovat voimassa ja onko hyppy on suinkin mahdollista. Tämä ei ole vaikeaa, mutta tarkastusten määrä on varsin laaja jotain, joka näyttää yksinkertaista. Koodi on melko itsestään selvä, ja jos ei, toivottavasti kommentit selittää mitä tapahtuu. Huomaa, että aliohjelma käyttää GetCell (<cellposition>) funktiota joka luotiin aikaisemmin.
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TPegSolitaire.Leap(const pFromCell, pToCell: TCellPosition);
 
procedure TPegSolitaire.Leap(const pFromCell, pToCell: TCellPosition);
 
var dx, dy: integer;
 
var dx, dy: integer;
Line 865: Line 865:
 
   end;
 
   end;
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
Usko tai älä, mutta nyt on toimiva Peg Solitaire lautapasianssi peliohjelma!
 
Usko tai älä, mutta nyt on toimiva Peg Solitaire lautapasianssi peliohjelma!
Line 880: Line 880:
 
* Poista muistio lomakkeelta.
 
* Poista muistio lomakkeelta.
 
* Poista muistion päivitykset MouseDown ja MouseUp  tapahtumat.  
 
* Poista muistion päivitykset MouseDown ja MouseUp  tapahtumat.  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
 
begin
 
begin
Line 891: Line 891:
 
   pbPeg.Repaint;
 
   pbPeg.Repaint;
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
* Siirry lomakkeelle ({{keypress|F12}}).
 
* Siirry lomakkeelle ({{keypress|F12}}).
Line 908: Line 908:
 
* Kirjoita seuraava teksti (sen pitäisi näyttää tutulta :-)):
 
* Kirjoita seuraava teksti (sen pitäisi näyttää tutulta :-)):
 
   
 
   
<source>
+
<syntaxhighlight lang="pascal">
 
   ooo
 
   ooo
 
   ooo
 
   ooo
Line 916: Line 916:
 
   ooo
 
   ooo
 
   ooo
 
   ooo
</source>
+
</syntaxhighlight>
 
* Valitse valikosta Tiedosto / Tallenna.
 
* Valitse valikosta Tiedosto / Tallenna.
 
* Tallenna tiedosto projektin bin kansioon nimellä ''classic.txt''.  
 
* Tallenna tiedosto projektin bin kansioon nimellä ''classic.txt''.  
Line 926: Line 926:
 
* Kirjoita seuraava teksti:  
 
* Kirjoita seuraava teksti:  
  
<source>
+
<syntaxhighlight lang="pascal">
 
   ...
 
   ...
 
   .o.
 
   .o.
Line 934: Line 934:
 
   ...
 
   ...
 
   ...
 
   ...
</source>
+
</syntaxhighlight>
 
* Valitse valikosta Tiedosto / Tallenna.
 
* Valitse valikosta Tiedosto / Tallenna.
 
* Tallenna tiedosto bin kansioon nimellä ''triangle.txt''.  
 
* Tallenna tiedosto bin kansioon nimellä ''triangle.txt''.  
Line 962: Line 962:
 
* Tuplaklikkaa ''Exit''. Kuten arvata saattaa tämä luo tapahtumakäsittelijän. Toisin sanoen aliohjelman, joka suoritetaan heti, kun käyttäjä klikkaa tätä valikkokohtaa.
 
* Tuplaklikkaa ''Exit''. Kuten arvata saattaa tämä luo tapahtumakäsittelijän. Toisin sanoen aliohjelman, joka suoritetaan heti, kun käyttäjä klikkaa tätä valikkokohtaa.
 
* Lisää seuraava rivi luotuun koodin runkoon:  
 
* Lisää seuraava rivi luotuun koodin runkoon:  
<source>
+
<syntaxhighlight lang="pascal">
 
   Close;
 
   Close;
</source>
+
</syntaxhighlight>
 
Tämä sulkee lomakkeen. Ja koska se on ainoa lomake niin myös sovellus loppuu.
 
Tämä sulkee lomakkeen. Ja koska se on ainoa lomake niin myös sovellus loppuu.
  
Line 988: Line 988:
 
*  Klikkaa ''Load from file...''.  Odotetusti tämä luo .... tapahtumankäsittelijänä.
 
*  Klikkaa ''Load from file...''.  Odotetusti tämä luo .... tapahtumankäsittelijänä.
 
* Lisää seuraava koodi tapahtumakäsittelijään:  
 
* Lisää seuraava koodi tapahtumakäsittelijään:  
<source>
+
<syntaxhighlight lang="pascal">
 
   if OpenDialog1.Execute then
 
   if OpenDialog1.Execute then
 
     ShowMessage(OpenDialog1.FileName);
 
     ShowMessage(OpenDialog1.FileName);
</source>
+
</syntaxhighlight>
 
Muista: edetä pienin askelin. Nyt on lisätty valikko lomakkeelle josta voi poistua sovelluksesta tai ladata pelilaudan pohjan levyltä. Vielä ei ole ??koodattu latausosaa, mutta yritetään ensin testata koodia joka on jo tehty.
 
Muista: edetä pienin askelin. Nyt on lisätty valikko lomakkeelle josta voi poistua sovelluksesta tai ladata pelilaudan pohjan levyltä. Vielä ei ole ??koodattu latausosaa, mutta yritetään ensin testata koodia joka on jo tehty.
  
Line 1,004: Line 1,004:
 
*  Klikkaa ''Load from file...''.  Odotetusti tämä avaa tapahtumankäsittelijän.
 
*  Klikkaa ''Load from file...''.  Odotetusti tämä avaa tapahtumankäsittelijän.
 
* Korvaa koodin tällä:  
 
* Korvaa koodin tällä:  
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TfrmMain.MenuItem4Click(Sender: TObject);
 
procedure TfrmMain.MenuItem4Click(Sender: TObject);
 
begin
 
begin
Line 1,030: Line 1,030:
 
   end;
 
   end;
 
end;
 
end;
</source>
+
</syntaxhighlight>
  
 
Ja se kaikki on siinä!
 
Ja se kaikki on siinä!
Line 1,042: Line 1,042:
 
* Avaa PegSolPainter käännösyksikkö.
 
* Avaa PegSolPainter käännösyksikkö.
 
* Lisää kolme (3) kuvamuuttujaa private osioon:
 
* Lisää kolme (3) kuvamuuttujaa private osioon:
<source>
+
<syntaxhighlight lang="pascal">
 
   TPegSolPainter = class
 
   TPegSolPainter = class
 
   private
 
   private
Line 1,053: Line 1,053:
 
     ImgEmpty  : TPicture;
 
     ImgEmpty  : TPicture;
 
     ImgPeg    : TPicture;
 
     ImgPeg    : TPicture;
</source>
+
</syntaxhighlight>
 
* Lisää kuvan latausaliohjelma public osioon.
 
* Lisää kuvan latausaliohjelma public osioon.
<source>
+
<syntaxhighlight lang="pascal">
 
   public
 
   public
 
     constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
 
     constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
Line 1,061: Line 1,061:
 
     function CanvasXYtoCell(const pX, pY: integer): TCellPosition;
 
     function CanvasXYtoCell(const pX, pY: integer): TCellPosition;
 
     procedure LoadImage(const pCellType: TCellType; const pFilename: string);
 
     procedure LoadImage(const pCellType: TCellType; const pFilename: string);
</source>
+
</syntaxhighlight>
 
* Luo aliohjelman runko.
 
* Luo aliohjelman runko.
 
* Lisää koodi:
 
* Lisää koodi:
<source>
+
<syntaxhighlight lang="pascal">
 
procedure TPegSolPainter.LoadImage(const pCellType: TCellType; const pFilename: string);
 
procedure TPegSolPainter.LoadImage(const pCellType: TCellType; const pFilename: string);
  
Line 1,090: Line 1,090:
 
   end;
 
   end;
 
end;
 
end;
</source>
+
</syntaxhighlight>
 
Koodi on taas aika itsestään selvä. Huomaa, että käytetään lyhyttä paikallista aliohjelmaa päivittämään kuvaa. Jos emme tekisi sitä sitten case-lause ((''case pCellType of'') olisi monimutkaisempi (eli sisältäisi ylimääräistä koodia). Nyt kukin solutyyppi tarvitsee vain yhden rivin päivittäkseen oikean kuvan.
 
Koodi on taas aika itsestään selvä. Huomaa, että käytetään lyhyttä paikallista aliohjelmaa päivittämään kuvaa. Jos emme tekisi sitä sitten case-lause ((''case pCellType of'') olisi monimutkaisempi (eli sisältäisi ylimääräistä koodia). Nyt kukin solutyyppi tarvitsee vain yhden rivin päivittäkseen oikean kuvan.
  
Line 1,097: Line 1,097:
 
* Paikallista Repaint aliohjelma.
 
* Paikallista Repaint aliohjelma.
 
* Case lauseessa, muuta ctNoAccess, ctEmpty ja ctPeg osiot seuraavanlaiseksi:
 
* Case lauseessa, muuta ctNoAccess, ctEmpty ja ctPeg osiot seuraavanlaiseksi:
<source>       
+
<syntaxhighlight lang="pascal">       
 
// And now draw the cell based on the cell's contents
 
// And now draw the cell based on the cell's contents
 
       case pegsol.Cell[iRow,iCol] of
 
       case pegsol.Cell[iRow,iCol] of
Line 1,133: Line 1,133:
  
 
       end;
 
       end;
</source>
+
</syntaxhighlight>
  
 
Lisätään kuva latauskoodi piirtoluokkaan.
 
Lisätään kuva latauskoodi piirtoluokkaan.
Line 1,141: Line 1,141:
 
* Lisää LoadImage kutsu  ''pegpaint'' olion luomisen jälkeen:  
 
* Lisää LoadImage kutsu  ''pegpaint'' olion luomisen jälkeen:  
  
<source>  // Start with a new 7x7 game
+
<syntaxhighlight lang="pascal">  // Start with a new 7x7 game
 
   pegsol := TPegSolitaire.Create(7);
 
   pegsol := TPegSolitaire.Create(7);
 
   pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);
 
   pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);
Line 1,148: Line 1,148:
 
   pegpaint.LoadImage(ctEmpty,    'tutpeg_cellempty.jpg');
 
   pegpaint.LoadImage(ctEmpty,    'tutpeg_cellempty.jpg');
 
   pegpaint.LoadImage(ctPeg,      'tutpeg_celloccupied.jpg');
 
   pegpaint.LoadImage(ctPeg,      'tutpeg_celloccupied.jpg');
</source>
+
</syntaxhighlight>
  
 
Varmista, että kuvat ladataan ja laitetaan ohjelman ''bin'' kansioon, tai vielä parempaa, luot omat kuvat. Huom. kun lataat allaolevia kuvia, klikkaa niitä ensin.
 
Varmista, että kuvat ladataan ja laitetaan ohjelman ''bin'' kansioon, tai vielä parempaa, luot omat kuvat. Huom. kun lataat allaolevia kuvia, klikkaa niitä ensin.
Line 1,168: Line 1,168:
 
* Klikkaa Lataa kuvake ja lataa siihen sopiva kuvake tai ladata marmorikuvan joka on tässä alla:  
 
* Klikkaa Lataa kuvake ja lataa siihen sopiva kuvake tai ladata marmorikuvan joka on tässä alla:  
 
[[Image:tutpeg_marble.png]]
 
[[Image:tutpeg_marble.png]]
* Pains OK.
+
* Paina OK.
 
Sovelluksella on nyt oma kuvake, joka näkyy valikossa ja tiedoston selaus ikkunassa.
 
Sovelluksella on nyt oma kuvake, joka näkyy valikossa ja tiedoston selaus ikkunassa.

Latest revision as of 16:44, 13 July 2019

English (en) suomi (fi)

Tämä opetusohjelma on toinen Lazarus opetusohjelma, jonka tavoitteena on ottaa käyttöön Lazaruksen sovelluskehityksen perusteet. On parasta aloittaa tämän opetusohjelman ensimmäisellä osalla: (Howdy World (Hello World on steroids)/fi). Tämä opetusohjelma selittää vähän sitä, miten työskennellä grafiikan parissa ja miten tehdään modulaarinen ohjelma. Tämän opetusohjelman lopputuotteena on yksinkertainen, mutta toimiva versio Peg Solitaire pelistä ([1]). Jos kaikki menee hyvin niin lopulta se näyttää tältä:

tutpeg solitaire.png

Projektin aloitus

Kuten edellisessä opetusohjelmassakin niin on parasta aloittaa puhtaalta pöydältä. Tee erillinen hakemisto(tai kansio) kutakin projektia kohti. Nopea kertaus:

  • Luo uusi hakemisto tähän peliin.
  • Aloita uusi projekti (Projekti / Uusi projekti ... ja valitse Sovellus).
  • Tallenna projekti nimellä PegSolitaire.
  • Tallenna aloitusikkuna/lomake nimellä ufrmMain.
  • Komponenttimuokkaimessa muuta lomakkeen nimeksi frmMain.
  • Muuta Caption arvoon Lazarus Peg Solitaire.
  • Valitse valikosta: Projekti / Projektikohtaiset asetukset ...
  • Valitse Kääntäjän asetukset / Hakupolut (klikkaa solmun puunäkymää)
  • Kirjoita teksti bin\ ennen kohdetiedostonimeä (tai bin/ esim. unix/linux ympäristössä)

Ja lisänä tässä projektissa:

  • Avaa projekti valintaikkunan ( Shift+Ctrl+F11).
  • Valitse kääntäjän asetukset / Virheenjäljitys.
  • Ota käyttöön ylivuoto- ja aluevirhe tarkistus (katso kuva alla).

tutpeg compiler options.png

Ensiaskeleet

On aina hyvä idea erottaa käyttöliittymään liittyvä koodi muusta esim. tietorakennetta määritelevästä koodista. Joten ensimmäinen askel on tehdä erillinen käännösyksikkö Solitaire tietorakenteita varten.

  • Valitse valikosta Tiedosto / Uusi käännösyksikkö.
  • Tallenna käännösyksikkö nimellä PegDatastructures.pas

Peruselementit PegSolitaire laudalla ovat marmorit, levyn rakenne ja tyhjät paikat. Simuloidaan yksinkertaisella matriisilla, jossa paikat ovat pelin solujen tyyppisiä (tyhjä, pelinappula ja ei pääse). Ja tämä kaikki kiteytetään luokkaan, joka käsittelee kaikkea tietojen manipulointia.

  • Lisää seuraava koodi PegDatastructures käännösyksikköön ( uses-lausekkeen jälkeen juuri ennen implementation osiota):
const
  C_MAX = 7;  // Max board size: 7x7

type
  TCellNums = 1..C_MAX;
  TCellType = (ctNoAccess, ctEmpty, ctPeg);
  TPegCells = array[TCellNums, TCellNums] of TCellType;

  TPegSolitaire = class
  private
    Size: TCellNums;
    PegCells: TPegCells;
    
  public
    constructor Create(const pSize: TCellNums);
  end;

On kohtuullista olettaa, että muu koodi, joka aikoo käyttää tätä luokkaa tarvitsee pääsyn solujen sisältöön (eli PegCells). Tapa käsitellä tätä on joko määrittelemällä joukko funktioita jotka hakee ja vie soluihin tai määritellä ns taulukko-ominaisuus. Valitaan jälkimmäinen lähestymistapa ja lisätään seuraava rivi TPegSolitaire luokan public osioon:

property Cell[const pRow, pCol: TCellNums]: TCellType;
  • Sijoita tekstikursori rakentajan eli constructor:n riville.
  • Paina Ctrl+ Shift+C: ohjelman kehitysympäristö luo rakentajan rungon (kuten aiemmissa esimerkeissä), mutta se myös luo tyhjän rungon kahdelle metodille, jotka antavat pääsyn Cell-ominaisuuden kautta PegCells taulukkoon.
  • Funktio GetCell noutaa tiedot PegCells taulukosta. Lisää seuraava koodi tähän funktioon:
  result := PegCells[pRow,pCol];
  • Aliohjelma SetCell sijoittaa dataa PegCells taulukkoon. Lisää seuraava koodi tähän aliohjelmaan:
  PegCells[pRow,pCol] := AValue;
  • Ja viimeistellä rakentaja Create. Lisää tämä koodi sen runkoon:
var iRow,iCol: integer;
begin
  // Store the size of the board locally
  Size := pSize;

  // Initialize all cells to 'not accessible'
  for iRow := 1 to C_MAX do
    for iCol := 1 to C_MAX do
      Cell[iRow,iCol] := ctNoAccess;

Nyt kun rakenteen perustiedot ovat paikallaan, on aika tarkastella mitä grafiikka tarvitsee. On monia tapoja näyttää solitaire pöytä. Tähän voidaan käyttää TPaintbox- komponenttia. Se antaa täydellisen määräysvallan graafisiin ominaisuuksiin.

Ohjelman ikkunalomake aikoo käyttää datarakennetta joka on määritelty PegDatastructure käännösyksikköön.

  • Siirry lähdekoodieditorissa ufrmmain-käännösyksikköön.
  • Lisää PegDatastructures sen uses luetteloon joka on tiedoston yläosassa:
uses
  PegDatastructures,
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;
  • Paina F12 (tämä tuo esiin lomakkeen).
  • Komponenttipaletin Standard-välilehdeltä valitaan TButton ja pudota se lomakkeelle vasempaan yläkulmaan.
  • Vaihda sen Caption ominaisuuten teksti Test paint.
  • Komponenttipaletin Additional-välilehdeltä valitaan TPaintbox-komponentti ja tuodaan se lomakkeelle.
  • Muuta sen Align arvoon alRight.
  • Muuta BordSpacing.Around arvoon 4.
  • Muuta Anchors.akLeft arvoon true.
  • Kirjoita Name-ominaisuuteen teksti: pbPeg.
  • Muuta lomakkeen kokoa, jotta se näyttää suunnilleen tältä:

tutpeg empty.png

Seuraava vaihe on kiinnittää solujen matriisi tähän TPaintbox-komponenttiin jakamalla se riveihin ja sarakkeisiin, jotka sisältävät levyn jokaisen solun. Jotta se olisi skaalautuva niin lasketaan leveys ja korkeus itsenäisesti. Tarvitaan pari muuttujaa pitämään tulosta. Ensin solujen leveys ja korkeus, jotta kaikki solut (7) sopivat täsmälleen lomakkeelle. Kehitetään piirtokoodi interaktiivisesti. Tähän käyttää painiketta, joka tuotiin lomakkeelle.

  • Tuplaklikkaa Test paint-painiketta (tämä luo tapahtumakäsittelijän).
  • Lisää kaksi muuttujaa:
var
  CellWidth : integer;
  CellHeight: integer;

Tarvitaan ylimääräisiä muuttujia pitämään välituloksia:

  • Lisää kolme paikallista muuttujaa:
  iRow, iCol: TCellNums;
  CellArea  : TRect;

CellArea käytetään rajoittamaan suorakulmaista aluetta näytöllä, johon solu piirretään.

Jotta käsittellään kaikki rivit ja sarakkeet niin tehdään se kahdella suoraviivaisella for-silmukalla.

  • Lisää seuraava koodi tapahtumankäsittelijään:
  
// Calculate the width/height of each cell to accomodate for all cells in the paintbox
  CellWidth := pbPeg.Width div 7;
  CellHeight := pbPeg.Height div 7;

  // Draw boxes for all cells
  for iRow := 1 to 7 do
    for iCol := 1 to 7 do
    begin
      // Calculate the position of the cell in the paintbox
      CellArea.Top    := (iRow-1) * CellHeight;
      CellArea.Left   := (iCol-1) * CellWidth;
      CellArea.Right  := CellArea.Left + CellWidth;
      CellArea.Bottom := CellArea.Top  + CellHeight;
      // And now draw the cell
      pbPeg.Canvas.Rectangle(CellArea);
    end;

Canvas on nimensä mukaisesti on ohjaus, joka auttaa meitä piirtämään asioita, kuten viivoja, suorakaiteita, ympyröitä jne. TPaintbox-komponentti sisältää Canvas:n. Siksi rivillä pbPeg.Canvas.Rectangle (CellArea) ohjelma piirtää suorakulmion paintbox, rajattu alue on määritelty CellArea. Ja koska paintbox sijoitetaan lomakkeelle, voimme nähdä tuloksen siellä.

  • Kääntää ja ajaa ohjelma (paina F9).
  • Paina Test paint-painiketta.
  • Suurenna lomaketta (solut häviävät mutta älä huoli se korjatuu).
  • Paina Test paint-painiketta uudelleen

Tämä todistaa, että laskelmat olivat paikallaan ja että on nyt keino tehdä kaikki tarpeellisen oikealla paikalla. Yksi asia on tehtävä kuitenkin ennen lisäämällä piirustus toiminnallisuutta. Piirustus solujen ei mitenkään tärkein muodossa tai toisessa (tai minkäänlaista että asia). Ainoa asia, meidän piirtämiseen asiat on Canvas ja joitakin mittauksia soluille. Joten aiomme luoda tukea luokan siivota tärkeimmäksi.

  • Luo uusi käännösyksikkö (Tiedosto / Uusi käännösyksikkö).
  • Tallenna se nimellä PegSolPainter.pas (Tiedosto / Tallenna nimellä ...).
  • Lisää uusi luokka tähän käännösyksikköön, se tekee kaiken piirtämisen (luokka tulee uses-lauseen jälkeen, ennen

implementation osiota).

type
  TPegSolPainter = class
  private
    PegSol      : TPegSolitaire;
    Canvas      : TCanvas;
  public
    constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
  end;

Huomaa, että TPegSolitaire muuttuja lisätään myös, koska käyttää tätä luokkaa käytetään noutamaan solujen tila.

  • Sijoita tekstikursorin rakentajan eli constructor:n riville ja paina Ctrl+ Shift+C.
  • Rakentajan kaksi alustus parametriä on sijoitettava luokan private muuttujiin (Täydennä luokan rakentajaa siten että se näyttää tältä) :
constructor TPegSolPainter.Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
begin
  PegSol := pPegSol;
  Canvas := pCanvas;
end;

Yritettäessä kääntää koodia niin tulee virheilmoitus koska ei olla lisätty PegDatastrucures käännösyksikköä uses-lauseseen. Ja koska käytetään myös TCanvas-luokkaa niin täytyy lisätä Graphics käännösyksikkö samoin.

  • Lisää PegDatastructures ja Graphics uses lauseen luetteloon.
uses
  PegDatastructures,
  Graphics,
  Classes, SysUtils;

Syy tämän luokan rakentamiseen oli poistaa kaikki piirtäminen koodilla lomakkeella. Joten on luotava menetelmä, joka tekee piirtämisen. Ennen lisäystä että menetelmä on jotain, johon on puututtava: laskea leveys solun, jaamme paintbox leveyttä useissa solujen. Teoriassa voisimme käyttää ominaisuutta Canvas.Width tähän. Kuitenkin tämä ominaisuus ei aina anna oikeata leveyttä oikeaan aikaan. Joten piirrettäesä soluja täytyy tarjota piirtää menetelmälle oikeat Canvas arvot leveydelle ja korkeudelle.

Nyt tiedetään tämä, voidaan lisätä piirustusmetodi luokkaan.

  • Lisää aliohjelma Repaint luokkaan (jolloin luokka määrittely näyttää tältä).
  
TPegSolPainter = class
  private
    PegSol      : TPegSolitaire;
    Canvas      : TCanvas;

  public
    constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
    procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);
  end;
</sourcet>
* Luo aliohjelman Repaint runko. Paina {{keypress|Ctrl|Shift|C}}.
* Kopio koodi ''TfrmMain.Button1Click(Sender: TObject)''-aliohjelmasta tähän runkoon (Jolloin siitä tulee tälläinen)
<syntaxhighlight lang="pascal">
procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);
var
  CellWidth    : integer;
  CellHeight   : integer;
  iRow, iCol   : TCellNums;
  CellArea     : TRect;
begin
  // Calculate the width of each cell to accomodate for all cells
  CellWidth := pbPeg.Width div 7;
  CellHeight := pbPeg.Height div 7;

  // Draw boxes for all cells
  for iRow := 1 to 7 do
    for iCol := 1 to 7 do
    begin
      // Calculate the position of the cell in the paintbox
      CellArea.Top    := (iRow-1) * CellHeight;
      CellArea.Left   := (iCol-1) * CellWidth;
      CellArea.Right  := CellArea.Left + CellWidth;
      CellArea.Bottom := CellArea.Top  + CellHeight;
      // And now draw the cell
      pbPeg.Canvas.Rectangle(CellArea);
    end;
end;

Koska ei enää tarvita TPaintbox-komponenttia pbPeg. Niin on poistettava viittaukset siihen. Joten kolmessa kohdassa tarvitaan muutoksia:

  • Muutos CellWidth:n ja CellHeight:n laskennassa:
  CellWidth := pCanvasWidth div 7;
  CellHeight := pCanvasHeight div 7;
  • Muutos suorakulmion piirtämisessä:
   Canvas.Rectangle(CellArea);

Nyt on luokka, jolla voidaan tehdä haluttu piirtäminen, on aika käyttää sitä. Tätä piirtoluokkaa käytetään yhdessä PegSolitare luokan kanssa solujen piirtämiseen.

  • Käännösyksikössä ufrmmain.pas, etsi Button1Click aliohjelma.
  • Poista siitä kaikki lausekkeet ja muuttujat (Tyhjennä se).
  • Lisätään kaksi uutta muuttujaa: peliluokan ja piirtämisluokan (Kaikkien näiden toimien tuloksena aliohjelma nyt näyttää tältä):
procedure TfrmMain.Button1Click(Sender: TObject);
var
  pegsol  : TPegSolitaire;  // The game data
  pegpaint: TPegSolPainter; // The paint class for the game
begin
end;
  • Lisää PegSolPainter uses-lauseen luetteloon.

Piirtoluokan käyttö on yksinkertaista: Luodaan uusi esiintymä ja kutsutaan piirto aliohjelmaa:

  • Lisää seuraava koodi Button1Click aliohjelmaan:
  
  // Create a new game object
  pegsol := TPegSolitaire.Create(7);

  // Create a new painter object to paint this game
  pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);

  // And paint the board
  pegpaint.Repaint(pbPeg.Width, pbPeg.Height);

  // Clean up
  pegpaint.Free;
  pegsol.Free

Suorita ja testta ohjelma. Nähdään että se toimii samaan tapaan kuin ennen. Tulos näyttää tältä: tutpeg empty cells.png

Kyse tapahtumista

Mitä on saatu aikaan tähän mennessä? On tietorakenne, joka sisältää kaikki tiedot PegSolitaire peliä varten. On luokka, johon voi piirtää. Ja on melko yksinkertainen lomake, jossa on testipainike. Joten mitä seuraavaksi? Tapahtumat!

Kuten edellisessä osassa on nähty, solun matriisi joka tehtiin hävisi jos lomakkeen kokoa muutettiin. Näin tapahtuu, koska lomake ei tiedä mitään "pikku pelistä". Heti kun lomakkeen mielestä on aika piirtää itse, se tekee niin ja ohittaa "pikku pelin". Mitä se tekee? Se vain lähettää viestin lapsi kontrolleille, että virkistäminen on välttämätön. Tämä viesti on käytettävissämme tapahtumana: OnPaint-tapahtuma.

  • Mene lomakkeelle (Valitse ufrmMain lähdekoodieditorissa ja paina F12).
  • Valitse TPaintbox pbPeg.
  • Siirry tapahtumat-välilehdelle komponenttimuokkaimessa.
  • Yksi luetelluista tapahtumista on OnPaint.

tutpeg onpaint.png


Tätä tapahtumaa kutsutaan joka kerta kun TPaintbox tarvitsee uudelleenpiirtämistä. Se on paikka johon tehdään piirtäminen.

  • Valitse OnPaint tapahtuma komponenttimuokkaimessa ja klikkaa pientä (...) painiketta kolmella pisteellä . Tämä luo tapahtumakäsittelijän rungon.
  • Kopioi / Liitä tarkka koodi Button1Click tapahtumakäsittelystä tähän uuteen aliohjelmaan.
procedure TfrmMain.pbPegPaint(Sender: TObject);
var
  pegsol  : TPegSolitaire;  // The game data
  pegpaint: TPegSolPainter; // The paint class for the game
begin
  // Create a new game object
  pegsol := TPegSolitaire.Create(7);

  // Create a new painter object to paint this game
  pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);

  // And paint the board
  pegpaint.Repaint(pbPeg.Width, pbPeg.Height);

  // Clean up
  pegpaint.Free;
  pegsol.Free
end;
  • Poista kaikki koodi ja muuttujat aliohjelmasta Button1Click. Muista: Ohjelmankehitysympäristö automaattisesti poistaa tämän tyhjän aliohjelman.
  • Poista Test paint -painike lomakkeelta.
  • Suorita ohjelma, katso mitä tapahtuu.

Nyt on selvää, että me luoda pelin objekti OnPaint menetelmässä, piirretään tyhjät solut ja sitten tuhota se. Mutta täytyy tallentaa peliolio kunnes peli on päättynyt. Sama koskee piirtämisoliota. Joten OnPaint tapahtuma ei ole loogisin paikka luoda näitä olioita. Lomakkeen luokan määrittely on parempi paikka tallentaa ne.

  • Siirry lomakkeen määrittelyyn.
  • Lisää kaksi muuttujaa jotka luotiin OnPaint tapahtumaan:
  TfrmMain = class(TForm)
    pbPeg: TPaintBox;
    procedure pbPegPaint(Sender: TObject);
  private
    { private declarations }
    pegsol  : TPegSolitaire;  // The game data
    pegpaint: TPegSolPainter; // The paint class for the game
  public
    { public declarations }
  end;


Nämä muuttujat pitää alustaa heti, kun lomake avataan (tai kun halutaan aloittaa uusi peli). Joten luodaan aliohjelma, joka tekee sen meille ja lisätään se private-osioon lomakkeelle.

  • Lisää aliohjelma StartNewGame lomakkeelle.
  
  private
    { private declarations }
    pegsol  : TPegSolitaire;  // The game data
    pegpaint: TPegSolPainter; // The paint class for the game

    procedure StartNewGame;
  • Luo aliohjelman runko. Paina Ctrl+ Shift+C.
  • Lisää alustus koodi:
procedure TfrmMain.StartNewGame;
begin
  // Clean up the previous game
  pegpaint.Free;
  pegsol.Free;

  // Start with a new game
  pegsol := TPegSolitaire.Create(7);
  pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);
end;

Nyt kun alustuksen koodi on luotu, se on suoritettava. Looginen aika tehdä tämä on, kun lomake on luodaan (eli sovellus käynnistetään). Tähän toiseen tapahtumaan pääse kiinni: FormCreate-tapahtumassa. Se voidaan luoda kahdella eri tavalla: Komponenttimuokkaimessa löytää OnFormCreate tapahtuma ja klikata "..." -painiketta. Toinen tapa tuottaa se on tuplaklikata lomaketta.

  • Mene lomakkeelle (Lähdekoodieditorissa paina F12).
  • Tuplaklikkaa jossain vapaata aluetta. Älä klikkaa TPaintbox:a.

tutpeg form create.png

  • Lisää sen runkoon aliohjelman StartNewGame kutsu:
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  StartNewGame;
end;

Nyt kun peli- ja piirto-oliot luodaan ohjelman alkaessa niitä ei enää tarvita OnPaint aliohjelmassa.

  • Paikallista aliohjelma procedure TfrmMain.pbPegPaint (Sender: TObject);
  • Poista paikalliset muuttujat ja kaikki koodi paitsi rivin joka tekee piirtämisen. (jolloin se on näin yksinkertainen)
procedure TfrmMain.pbPegPaint(Sender: TObject);
begin
  // Paint the board
  pegpaint.Repaint(pbPeg.Width, pbPeg.Height);
end;
  • Suorita ohjelma ja katso mitä tapahtuu.

Intermezzo

Tähän mennessä ollaan keskittynyt rakentamaan ohjelmaa jolla pelata klassista pasianssi peliä 7x7 ruudukossa. Onko nyt mahdollista luoda pienempi tai suurempi ruudukko esim. muun tyyppiselle pelille? Testataan tätä.

  • Mene käännösyksikköön ufrmmain.pas ja siellä aliohjelmaan StartNewGame.
  • Muuta rivi pegsol: = TPegSolitaire.Create (7); riviksi pegsol: = TPegSolitaire.Create (5); . Joten pienempi lauta 5x5 neliöitä on luotu.
  • Suorita ohjelma ja katso mitä tapahtuu.

Kuten ruudulla nähdään niin vielä on 7x7 ruudukko! Tämä on seurausta kun käyttää (taika) numeroita, jota olisi voitu välttää (ks http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Unnamed_numerical_constants ).

Taikanumeroiden käyttäminen on suuri katastrofi: se on asia, kun, ei jos, ohjelma epäonnistuu.

  • Avaa käännösyksikkö PegSolPainter.
  • Paikanna aliohjelma TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);.

Siellä nähdään numero 7 muutamia kertoja. Tämä on se maaginen numero, joka tekee temppuja. Laskettaessa solun leveys ja korkeus, tarvitsemme laudalle koon joka on tallennettu pegsol pelin Size muuttujaan. Ja sama pätee iRow ja iCol silmukoihin. Joten korjataan ne kerralla:

  
// Calculate the width of each cell to accomodate for all cells
  CellWidth := pCanvasWidth div pegsol.Size;
  CellHeight := pCanvasHeight div pegsol.Size;

  // Draw boxes for all cells
  for iRow := 1 to pegsol.Size do
    for iCol := 1 to pegsol.Size do
    begin
      // Calculate the position of the cell in the paintbox
      CellArea.Top    := (iRow-1) * CellHeight;
      CellArea.Left   := (iCol-1) * CellWidth;
      CellArea.Right  := CellArea.Left + CellWidth;
      CellArea.Bottom := CellArea.Top  + CellHeight;
      // And now draw the cell
      Canvas.Rectangle(CellArea);
    end;

Tässä varoitus: pegsol:ssa ei ole julkisesti saatavilla Size -muuttujaa. Ja näin se pitäisi olla: kaikki luokan muuttujat pitäisi olla yksityisiä. Tapa käyttää näitä yksityisiä arvoja on funktioiden tai ominaisuuksien (rajapinta luokan) kautta. Tähän yksinkertaiseen arvoon sovelletaan ominaisuutta.

  • Mene käännösyksikköön PegDatastructures.
  • Uudelleen nimeä luokan TPegSolitaire yksityisen muuttujan Size uudeksi nimeksi FSize.
  • Lisää julkinen vain luku ominaisuus luokkaan: property Size: TCellNums read FSize;

Luokka nyt näyttää tältä:

  
TPegSolitaire = class
  private
    FSize: TCellNums;
    PegCells: TPegCells;
    function GetCell(const pRow, pCol: TCellNums): TCellType;
    procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);

  public
    constructor Create(const pSize: TCellNums);
    property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;
    property Size: TCellNums read FSize;
  end;

On yleinen käytäntö käyttää muuttujien etuliitteenä kirjainta F joihin päästään käsiksi ominaisuuksilla. Julkinen pääsy ominaisuuteen Size on vain sen arvon lukeminen koska sitä ei pidä koskaan muuttaa kun peli käynnistetään. Ainoa paikka, jossa tämä yksityinen muuttuja pitäisi saada sen lopullinen arvon on Create rakentaja.

  • Paikallista rakentaja (constructor).
  • Muuta riviSize := pSize; riviksi FSize := pSize; (Jotta ei saada käännösvirhettä).
  • Ja kun ollaan siellä niin alustusta tarvitsee vähäisen korjata. Ei tarvitse alustaa soluja joita ei aiota käyttää. Joten rakentaja tulisi näyttää tältä (C_MAX korvataan Size:lla):
constructor TPegSolitaire.Create(const pSize: TCellNums);
var iRow,iCol: integer;
begin
  FSize := pSize;
  for iRow := 1 to Size do
    for iCol := 1 to Size do
      Cell[iRow,iCol] := ctNoAccess;
end;

Olemme nyt valmis testaamaan ohjelman ja katso jos se piirtää nyt mukavan 5x5 matriisi.

  • Suorita ohjelma ja katso mitä tapahtuu (5x5 matriisi piirretään).

Nyt kun pelin perusteet ovat paikoillaan, on aika täsmentää käyttöliittymää.


Ollaan taiteellisia

Hienoa. Nyt on peli luokka, tukiluokka piirtämiseen ja ruudullinen pelilauta lomakkeella. Pasianssipelin solu voi olla kolmessa (3) tilassa: ei saatavilla, tyhjä tai käytössä. Kuhunkin solutyyppiin halutaan erilainen graafinen esitys. Tehdään nyt se.

  • Avaa lähdekoodieditorissa käännösyksikkö PegSolPainter (käännösyksikkö joka piirtää pelilaudan).
  • Siirry Repaint aliohjelmaan.
  • Etsi rivi, jossa solu on piirretään: Canvas.Rectangle (CellArea);.

Täällä täytyy tehdä muutoksia. Se riippuu solun tilasta mitä tarvitaan piirtämiseen (ei käytettävissä oleva solu, tyhjä solu tai pelinappula solu). Tapaus ... toiminta tulee pelastus.

  • Muuta solujen piirtämistä:
  // Draw boxes for all cells
  for iRow := 1 to pegsol.Size do
    for iCol := 1 to pegsol.Size do
    begin
      // Calculate the position of the cell in the paintbox
      CellArea.Top    := (iRow-1) * CellHeight;
      CellArea.Left   := (iCol-1) * CellWidth;
      CellArea.Right  := CellArea.Left + CellWidth;
      CellArea.Bottom := CellArea.Top  + CellHeight;

      // And now draw the cell based on the cell's contents
      case pegsol.Cell[iRow,iCol] of

        ctNoAccess: // Draw cells that are not accessible
          begin
            Canvas.Brush.Color := clGray;
            Canvas.Rectangle(CellArea);
          end;

        ctEmpty:    // Draw cells that are currently empty
          begin
            Canvas.Brush.Color := clBlue;
            Canvas.Rectangle(CellArea);
          end;

        ctPeg:      // Draw cells that are occupied
          begin
            Canvas.Brush.Color := clBlue;
            Canvas.Rectangle(CellArea);    // Erase the background first
            Canvas.Brush.Color := clGreen; 
            Canvas.Ellipse(CellArea);      // Draw the pegs as green circles
          end;
      end;
    end;

Ohjelmaa voitaisiin ajaa tässä vaiheessa (tai vain kokeilla sitä), mutta se on vain tylsä? (5x5) harmaa ruudukko. Tämä johtuu siitä ettei vielä ole määritelty mitään peliasetuksia. Korjataan sitä seuraavaksi.

  • Avaa ufrmmain käännösyksikkö.
  • Paikallista StartNewGame aliohjelma.
  • Luo 5x5 pelin sijasta 7x7 peli.
  • Alusta muutamia soluja. Esimerkiksi:
procedure TfrmMain.StartNewGame;
begin
  // Clean up the previous game
  pegpaint.Free;
  pegsol.Free;

  // Start with a new 7x7 game
  pegsol := TPegSolitaire.Create(7);
  pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);

  // Initialize some cells
  pegsol.Cell[3,4] := ctEmpty;
  pegsol.Cell[4,2] := ctPeg;
  pegsol.Cell[4,3] := ctPeg;
  pegsol.Cell[4,4] := ctEmpty;
  pegsol.Cell[4,5] := ctPeg;
  pegsol.Cell[4,6] := ctPeg;
  pegsol.Cell[5,4] := ctEmpty;
end;
  • Suorita ohjelma (sen tulee näyttää jokseenkin samanlaiselta kuin kuva alla).

tutpeg first pegs.png

Täytä pelilauta

Kuten tiedetään niin klassinen solitaire pelilaudan pitäisi näyttää tältä:

tutpeg classic.png

Jos täytetään kaikki solut yksitellen, niin se johtaisi suureen koodimäärään. Mitä jos voitaisiin alustaa peli vain tekstillä, joka symbolisesti kuvaa pelilaudan? Jotain tällaista:

  
// Initialize the cells to the classic game
  pegsol.InitializeBoard( '  ooo  ' + LineEnding +
                          '  ooo  ' + LineEnding +
                          'ooooooo' + LineEnding +
                          'ooo.ooo' + LineEnding +
                          'ooooooo' + LineEnding +
                          '  ooo  ' + LineEnding +
                          '  ooo  ' );
  1. o on solu jossa on pelinappula.
  2. . on tyhjä, mutta pelattavaa solu.
  3. Välilyönnit osoittavat solut, jotka eivät ole käytettävissä.

Oletetaan että tämä tulee toimimaan ja luodaan aliohjelma TPegSolitaire luokkaan joka voi hoitaa tämän.

  • Ollaan optimistinen (kutsutaan myös Top down suunnitteluksi) ja lisätään edellä ollut koodi TfrmMain.StartNewGame:
procedure TfrmMain.StartNewGame;
begin
  // Clean up the previous game
  pegpaint.Free;
  pegsol.Free;

  // Start with a new 7x7 game
  pegsol := TPegSolitaire.Create(7);
  pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);

  // Initialize the cells to the classic game
  pegsol.InitializeBoard( '  ooo  ' + LineEnding +
                          '  ooo  ' + LineEnding +
                          'ooooooo' + LineEnding +
                          'ooo.ooo' + LineEnding +
                          'ooooooo' + LineEnding +
                          '  ooo  ' + LineEnding +
                          '  ooo  ' );
end;
  • Avaa PegDatastructures lähdekooditiedosto.
  • Lisää tämä aliohjelman esittely luokan public osioon: InitializeBoard(const pBoard: ansistring);
  
public
    constructor Create(const pSize: TCellNums);
    procedure InitializeBoard(const pBoard: ansistring);

    property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;
    property Size: TCellNums read FSize;
  • Luo aliohjelman InitializeBoard:n runko. Paina Ctrl+ Shift+C.

Mitä tämä aliohjelman tarvitse tehdä? Se jakaa textstringin osiksi erillisiin riveihin ja sitten käsitellä nämä rivit, koska käytettiin LineEnding-vakiota erottamaan rivit niin voidaan käyttää TStringList luokkaa erottamaan ne.

  • Lisää InitalizeBoard koodi:
procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);
var lst      : TStringList;
    iRow,iCol: integer;
    s        : string;
begin
  // Create a list with the board text in it. This will split all lines
  // into individual lines, because of the LineEnding 'splitter'.
  lst := TStringList.Create;
  lst.Text := pBoard;

  // Process all lines one at a time
  for iRow := 0 to lst.Count-1 do
    if iRow < Size then // Make sure there is no overflow in the rows
    begin
      // Process a single line of text
      s := lst[iRow];
      for iCol := 1 to length(s) do
        if iCol <= Size then  // Make sure there is no overflow in the columns
          case s[iCol] of
            ' ': Cell[iRow+1,iCol] := ctNoAccess;
            '.': Cell[iRow+1,iCol] := ctEmpty;
            'o': Cell[iRow+1,iCol] := ctPeg;
          end;
    end;

  // Clean up the list
  lst.Free;
end;
  1. TStringList käytetään puskurina. Tämä toimii, koska LineEnding:a käytettiin rivien erottimena.
  2. Count kertoo rivien määrän, mutta ne on numeroitu 0..Count-1. Ja solut on numeroitu aloittaen ykkösestä(1). Siksi on solua osoittaessa tehtävä iRow + 1.

Edellä esitetty menettely sisältää paljon ylimääräisiä muuttujia ja ei pitäisi olla kovin vaikea ymmärtää. On mahdollista vähentää aliohjelma minimiin kuten näin:

procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);
var iRow,iCol: integer;
begin
  with TStringList.Create do
  begin
    Text := pBoard;
    for iRow := 0 to Min(Count-1, Size-1) do
      for iCol := 1 to Min(length(Strings[iRow]),Size) do
        case Strings[iRow][iCol] of
          ' ': Cell[iRow+1,iCol] := ctNoAccess;
          '.': Cell[iRow+1,iCol] := ctEmpty;
          'o': Cell[iRow+1,iCol] := ctPeg;
        end;
    Free;
  end;
end;

Tämä menettely tekee täsmälleen saman. Se käyttää kätevää ominaisuutta, sitä että voidaan käyttää With-lausetta kun luodaan dynaamisesti oliota. Toimiakseen tämä aliohjelma vaatii että lisätään Math käännösyksikkö uses lauseen luetteloon.

  • Suorita ohjelma. Kaikki soluissa on pelinappula kuten klassisessa Peg Solitaire pelissä.


Tapahtumien uudelleenkäynti

Se ei kovin vaikuttava vielä, mutta tehty on jo paljon. Tärkein joka puuttuu tällä hetkellä on kyky hypätä solujen poistaa ne pelilaudalta. Ja että siihen käytetään hiirtä. Mutta miten? Tutkitaan mahdollisuuksia.

  • Tuo TMemo-komponentti lomakkeelle, TPaintBox pbPeg:n vasemmalla puolen (TMemo löytyy Standard välilehdeltä komponenttipaletilta).
  • Komponenttimuokkaimessa muuta Align arvoon alClient. Se vie kaiken jäljellä olevan tilan, joka on jäljellä TPaintbox.
  • Vaihda BorderSpacing.Around arvoon 4.
  • Tyhjennä Lines ominaisuus (klikkaa kolme pistettä painiketta (...) ja poista teksti). Tuloksena pitäisi näyttää tältä:

tutpeg with memo.png

  • Valitse TPaintBox pbPeg.
  • Komponenttimuokkaimessa valitse Tapahtumat-välilehti.

On kaksi tapahtumaa, jotka ovat mielenkiintoisia: OnMouseDown ja OnMouseUp. Katsotaan mitä tapahtuu OnMouseDown tapahtumassa.

  • Komponenttimuokkaimessa valitse OnMouseDown tapahtuma.
  • Klikkaa kolme pisteettä painiketta (tämä luo rungon tapahtuman aliohjelmalle).

Syntyvään aliohjelman otsikkoon sisältyy useita muuttujia. Kaksi niistä ovat X ja Y. Ne sisältävät TPaintBox:n paikan jossa hiirtä painettiin. Tehdään ne näkyviksi lisäämällä joitakin tietoja TMemo-komponenttiin joka on lisätty lomakkeelle.

  • Lisää seuraavat lauseet aliohjelmaan; se lisää muotoillun rivin TMemo-komponentin näyttämään paikkaa, missä hiirtä painettiin:
Memo1.Append( format('Mouse down @ %dx%d',[X,Y]) );
  • Suorita ohjelma. (Paina F9).
  • Klikkaa hiirellä pelilautaa useissa eri paikoissa. Muistion (TMemon) ruutu näyttää missä hiiri painettiin.
  • Lopeta ohjelma.

Ilmeisesti ei voi käyttää "raakaa" X ja Y-koordinaatteja; ne ovat liian isoja 7x7 pelilaudalle. Ne täytyy sovittaa solun rivin ja sarakkeen koordinaattuin. Sitä varten tarvitaan toiminto, joka sovittaa X / Y-koordinaatin solun koordinaattiin. Tähän tarvitaan paintbox:n leveys ja korkeus, solujen lukumäärä ruudukossa ja yksittäisen solun leveys ja korkeus. On yksi paikka, jossa kaikki tämä tulee yhdessä ... pn Paint luokka. Tämä on siis selvä paikka tehdä tämä laskelma.

  • Avaa PegDatastructures käännösyksikkö.
  • Lisää TCellPosition tietuetyyppi johon tallennetaan solun koordinaatit, tyyppien esittelyosioon:
type
  TCellNums = 1..C_MAX;
  TCellType = (ctNoAccess, ctEmpty, ctPeg);
  TPegCells = array[TCellNums, TCellNums] of TCellType;

  TCellPosition = record
    Row: TCellNums;
    Col: TCellNums;
  end;
  • Avaa PegSolPainter käännösyksikkö.
  • Lisää luokan julkiseen (public)osioon funktio CanvasXYtoCell (...):
  public
    constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);

    procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);
    function CanvasXYtoCell(const pX, pY: integer): TCellPosition;
  • Luo funktion runko. Paina Ctrl+ Shift+C.
  • Lisää seuraava koodi:
function TPegSolPainter.CanvasXYtoCell(const pX, pY: integer): TCellPosition;
begin
  result.Col := (pX div CellWidth)  + 1;
  result.Row := (pY div CellHeight) + 1;
end;

Edellä koodi kartoittaa suuret X / Y-koordinaatit vastaamaan solun rivi ja sarake numeroita. Yksi ongelma on kuitenkin: Täällä CellWidth ja CellHeight ei ole saatavilla. Ne ovat välituloksia Repaint aliohjelman eikä niitä tallenneteta. Tämä pitää korjata.

  • Lisää CellWidth ja CellHeight yksityisinä muuttujina TPegSolPainter luokkaan:
  TPegSolPainter = class
  private
    PegSol      : TPegSolitaire;
    Canvas      : TCanvas;
    CellWidth   : integer;
    CellHeight  : integer;
  • Poista muuttujat CellWidth and CellHeight aliohjelman Repaint muuttujien esittelystä:
procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);
var
  iRow, iCol   : TCellNums;
  CellArea     : TRect;
begin

Nyt voidaan muuttaa aliohjelmaa, missä hiiren napin alaspainamisen koordinaattia käsiteltiin. Uuden funktion X / Y-paikka voidaan nyt sovittaa solun koordinaatteihin.

  • Avaa ufrmmain käännösyksikkö.
  • Paikallista pbPegMouseDown aliohjelma.
  • Muuta koodi:
procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var CellRC: TCellPosition;
begin
  CellRC := pegpaint.CanvasXYtoCell(X,Y);
  Memo1.Append( format('Mouse down @ %dx%d',[CellRC.Row, CellRC.Col]) );
end;

Tehdään sama OnMouseUp tapahtumaan:

  • Luo aliohjelman runko OnMouseUp tapahtumaan.
  • Lisää tämä koodi:
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  with pegpaint.CanvasXYtoCell(X,Y) do
    Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) );
end;

Huomaa, että tämä menetelmä tekee täsmälleen sama kuin OnMouseDown, mutta käyttäen with-rakennetta säästetään vaivaa määrittää paikallinen muuttuja (vähemmän on enemmän ...).

Testataan tätä:

  • Suorita ohjelma.
  • Klikkaa ylhäällä vasemman puolista solua ja pidä hiiren nappi alhaalla.
  • Siirrä hiiren kohdistin toiseen soluun.
  • Vapauta hiiren painike.

Joka kerta kun painaa hiiren painiketta ja vapauttaa sen, niin solu, jossa hiiren painiketta painettiin tai vapautettiin näkyy muistiossa.

Liimataan asioita yhteen

Tämä ohjelma toimii vähän kuin laskin ohjelma:

  1. Käyttäjä valitsee pelinappulan jota siirretään painamalla hiiren painiketta.
  2. Käyttäjä siirtää pelinappulan toiseen pelinappulan yli tyhjään soluun (hyppy).
  3. Käyttäjä vapauttaa hiiren painikkeen ja siirto tapahtuu.

Tällä ohjelma ei muista mitä solua painettiin. Joten kun käyttäjä painaa hiiren painiketta solun päällä, meidän täytyy säilyttää tämä paikka jonnekin.

  • Avaa ufrmmain käännösyksikkö.
  • Lisää muuttuja FromCell lomakkeen yksityiseksi muuttujaksi:
  
private
    { private declarations }
    pegsol  : TPegSolitaire;  // The game data
    pegpaint: TPegSolPainter; // The paint class for the game
    FromCell: TCellPosition;  // The peg that is going to leap
  • Mene OnMouseDown tapahtumankäsittelijään.
  • Lisää seuraava rivi tähän aliohjelmaan:
   FromCell := pegpaint.CanvasXYtoCell(X,Y);

Nyt kun käyttäjä vapauttaa hiiren painikkeen, on suoritettava hyppy (myös tarkistettava, onko se ok ja päivittää pelilautaa). Ollaan optimistisia (muista Top Down huomautus?) Ja olettaa, on aliohjelma, joka tekee sen meille: Leap (<FromCell>, <ToCell>).

  • Paikallista OnMouseUp tapahtumakäsittelijä ja lisää seuraava rivi:
  pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));

FromCell oli jo pelinappula kun hiiren näppäintä painettiin. Kohdesolun lasketaan yksinkertaisesti kutsumalla pegpaint.CanvasXYToCell (...). Voisimme ottaa käyttöön paikallinen muuttuja sitä, mutta se ei ole välttämättä tarpeen.

  • Lisää seuraava rivi aliohjelmaan:
  pbPeg.Repaint;

Tämä pakottaa paintbox:n piirtämään itsensä, joten kaikki päivitetyt solut näyttävät oikean sisällön.

Täydellinen OnMouseDown aliohjelma nyt näyttää tältä:

procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  with pegpaint.CanvasXYtoCell(X,Y) do
    Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) );
  pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));
  pbPeg.Repaint;
end;

Ennenkuin mennään Leap aliohjelmaan niin vähän lisätoimintoja otetaan käyttöön. Muista, että voidaan noutaa solun tila (tyhjä, pelinappula tai ei pääsyä) TPegSolitaire:n Cell ominaisuuden kautta! Esimerkiksi pegsol.Cell [2,2] antaisi rivin 2 ja sarakkeen 2 solun tilan. Mutta tähän väliin, on otettu käyttöön uusi tietorakenne: TCellPosition. Niinpä esimerkiksi, jos halutaan saada tila FromCell:stä tarvitaan seuraava hankala lause:

   state := pegsol.Cell[ FromCell.Row, FramCell.Col ]

Olisi mukavaa, jos voisi kirjoittaa jotain:

  state := pegsol.GetCell(FromCell)

Lisätään tämä peli luokkaan.

  • Avaa käännösyksikkö PegDatastructures.
  • Lisää yksityinen (private) funktio:GetCell (const pPosition: TCellPosition): TCellType;
  TPegSolitaire = class
  private
    FSize: TCellNums;
    PegCells: TPegCells;
    function GetCell(const pRow, pCol: TCellNums): TCellType;
    procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);
    function GetCell(const pPosition: TCellPosition): TCellType;

Nyt on kaksi samannimistä GetCell funktiota, mutta niiden parametriluettelo on erilainen. Joten Lazarus tietää tarkalleen, milloin kutsua kyseistä funktiota.

  • Luo funktion runko (Paina Ctrl+ Shift+C).
  • Lisätään seuraava koodi:
  result := Cell[pPosition.Row, pPosition.Col];

Takaisin "pääjuoneen". On otettu käyttöön aliohjelma Leap (...), mutta sitä ei vielä ole. Se on aliohjelma, joka muuttaa peliä pelilaudalla, joten paikka mihin laittaa se on TPegSolitaire luokka.

  • Avaa käännösyksikkö PegDatastructures.
  • Lisää aliohjelma Leap luokan TPegSolitaire public osioon.
  public
    constructor Create(const pSize: TCellNums);
    procedure InitializeBoard(const pBoard: ansistring);
    procedure Leap(const pFromCell, pToCell: TCellPosition);
  • Luo aliohjelman runko (Paina Ctrl+ Shift+C).

Nyt tämä aliohjelma tekee kaiken kovan työn: tarkistaa, että solut ovat voimassa ja onko hyppy on suinkin mahdollista. Tämä ei ole vaikeaa, mutta tarkastusten määrä on varsin laaja jotain, joka näyttää yksinkertaista. Koodi on melko itsestään selvä, ja jos ei, toivottavasti kommentit selittää mitä tapahtuu. Huomaa, että aliohjelma käyttää GetCell (<cellposition>) funktiota joka luotiin aikaisemmin.

procedure TPegSolitaire.Leap(const pFromCell, pToCell: TCellPosition);
var dx, dy: integer;
    JumpedCell: TCellPosition;
begin
  // Verify that the start cell is occupied and the target cell is empty
  // If not, leave the procedure via the EXIT.
  if (GetCell(pFromCell) <> ctPeg) or
     (GetCell(pToCell) <> ctEmpty) then
    EXIT;

  // Calculate the horizontal and vertical distance between the cells
  dx := abs(pFromCell.Col - pToCell.Col);
  dy := abs(pFromCell.Row - pToCell.Row);

  // A valid move has one direction equal to zero and the other equal to 2
  if    ((dx = 2) and (dy = 0))
     or ((dx = 0) and (dy = 2)) then
  begin
    // Determine the position of the jumped cell; it's in the middle
    JumpedCell.Col := (pFromCell.Col + pToCell.Col) div 2;
    JumpedCell.Row := (pFromCell.Row + pToCell.Row) div 2;

    // Final check: is there a peg in the jumped cell?
    if GetCell(JumpedCell) = ctPeg then
    begin
      // Jump: clear the FromCell, empty the jumped cell and populate the ToCell
      Cell[pFromCell.Row, pFromCell.Col]   := ctEmpty;
      Cell[JumpedCell.Row, JumpedCell.Col] := ctEmpty;
      Cell[pToCell.Row, pToCell.Col]       := ctPeg;
    end;
  end;
end;

Usko tai älä, mutta nyt on toimiva Peg Solitaire lautapasianssi peliohjelma!

  • Suorita ohjelma.
  • Paina hiiren painiketta yhden pelinappula solun päällä.
  • Hyppää yli toisen pelinappula solun tyhjään soluun.
  • Vapauta hiiren painike.
  • Toista tätä...

Puhdistusta

Lomakkeelle meillä on vielä muistio komponentti, joka näyttää hiiren klikkauksella. Sitä enää todellakaan tarvita.

  • Poista muistio lomakkeelta.
  • Poista muistion päivitykset MouseDown ja MouseUp tapahtumat.
procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FromCell := pegpaint.CanvasXYtoCell(X,Y);
end;

procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y) );
  pbPeg.Repaint;
end;
  • Siirry lomakkeelle (F12).
  • Valitse TPaintBox pbPeg
  • Komponenttimuokkaimessa vaihda ominaisuus Align arvoon alClient (Ominaisuudet välilehti).
  • Poista käännösyksikkö stdctrls uses luettelosta. Muuten kääntäjä antaa vihjeen "Kääntäjän viestit" ikkunassa ettei sitä käytetä (Se lisättiin automaattisesti kun TMemo komponentti tuotiin lomakkeelle).

Mitä seuraavaksi?

Jotta tämä olisi täysin toimiva peli tarvitsemme tapa muuttaa pelilautaa ilman että kääntää sovellus uudelleen. Ja grafiikka voitaisiin parantaa. aletaan käsitellä ensinmäistä kysymystä ensin.

Luodaan enemmän pelilautoja

Olisi mukavaa, jos voidaan lisätä pelilautoja peliin ilman että joutuisi kääntämään uudelleen sovelluksen. Ilmeinen paikka tallentaa pelilaudan kaavat olisi ulkoiset tekstitiedosto. Käyttäjä voi sitten aloittaa uuden pelin avaamalla jonkun tekstitiedoston. Hauska asia on, se on helppo lisätä, koska ollaan jo tehty suurimman osan kovasta työstä. Katsotaanpa ensin tehdä kaksi pelilaudan kaavoista.

  • Valitse valikosta Tiedosto / Uusi ...; Tämä avaa "Uusi..."- ikkunan.
  • Valitse vaihtoehto Moduuli / Teksti ja valitse OK.
  • Kirjoita seuraava teksti (sen pitäisi näyttää tutulta :-)):
  ooo
  ooo
ooooooo
ooo.ooo
ooooooo
  ooo
  ooo
  • Valitse valikosta Tiedosto / Tallenna.
  • Tallenna tiedosto projektin bin kansioon nimellä classic.txt.

Lisää toinen tiedosto:

  • Valitse valikosta Tiedosto / Uusi ....
  • Valitse vaihtoehto Moduuli / Teksti ja valitse OK.
  • Kirjoita seuraava teksti:
  ...
  .o.
..ooo..
.ooooo.
ooooooo
  ...
  ...
  • Valitse valikosta Tiedosto / Tallenna.
  • Tallenna tiedosto bin kansioon nimellä triangle.txt.

On monia tapoja luoda ja avata uusia pelilautoja. Tässä opetusohjelmassa käytetään valikko lähestymistapaa (perusteellisempi kuvaus valikosta on jossain toisessa opetusohjelmassa).

  • Avaa ufrmmain käännösyksikkö.
  • Valitse TMainMenu-komponentti komponenttipaletin Standard-välilehdeltä.
  • Klikkaa lomakkeella (sillä ei ole väliä, jos osut paintbox:n). Kuvake näkyy osoittaa, että valikko on nyt saatavilla olevat.
  • Tuplaklikkaa MainMenu1 kuvaketta. Tämä avaa valikkomuokkaimen. Yksi kohde on jo käytettävissä, nimeltään New Item1.
  • Komponenttimuokkaimella muutaa sen Caption tekstiksi &File (sisältyy et-merkillä, sitä käytetään vetämään alaviivat kun painat Alt päävalikkoon).
  • Klikkaa hiiren oikealla File kohdasta valikkomuokkaimessa ja valitse Luo alivalikko ponnahdusikkunasta.
  • Muuta Caption tekstiksi E&xit (tämä on tuttu sovelluksen lopeta kohta).
  • Klikkaa hiiren oikealla File kohtaa.
  • Valitse Lisää uusi kohta (jälkeen) ponnahdusikkunasta.
  • Muuta Caption tekstiksi kyseisen kohtaan &Game.
  • Klikkaa hiiren oikealla Game kohdasta valikkomuokkaimessa ja valitse Luo alivalikko ponnahdusikkunasta.
  • Muuta Caption tekstiksi Load from file....

Valikko nyt näyttää tältä:

tutpeg mainmenu.png

Valikko on myös näkyvissä lomakkeen yläosassa.

  • Klikkaa File-valikosta kohtaa; Tämä avaa alivalikon ja Exit on näkyvissä.
  • Tuplaklikkaa Exit. Kuten arvata saattaa tämä luo tapahtumakäsittelijän. Toisin sanoen aliohjelman, joka suoritetaan heti, kun käyttäjä klikkaa tätä valikkokohtaa.
  • Lisää seuraava rivi luotuun koodin runkoon:
  Close;

Tämä sulkee lomakkeen. Ja koska se on ainoa lomake niin myös sovellus loppuu.

Ok, nyt on aika tarkastella, miten avata pelilauta tiedostoja. On olemassa vakiokomponentteja ja valikko käytettävissä tehdä tämä, mutta tehdään se manuaalisesti.

  • Mene lomakkeelle.
  • Komponentti paletulta valitse Dialogs välilehti.
  • Valitse TOpenDialog komponentti (klikkaa sitä).
  • Klikkaa lomaketta (missä tahansa). OpenDialog1 komponentti on nyt näkyvissä lomakkeella. Tämä komponentti tarjoaa meille eräänlainen selailuikkunan, jolla voimme valita tiedostoja. Lomake näyttää nyt tältä:

tutpeg mainform.png

  • Komponenttimuokkaimessa klikkaa Filter ominaisuutta ja sitten 3-piste (...) painiketta. Tämä avaa suodattimen muokkain dialogi-ikkunan. Tämä on paikka, jossa kerrotaan minkä tyyppisiä tiedostoja käyttäjälle on mahdollisuus valita.
  • Nyt halutaan vain tekstitiedostoja joten lisätään tälläinen suodatinrivi: Peg Solitaire TextFiles (* .txt) | * .txt (katso kuva alla).
  • Paina OK.

tutpeg filterdialog.png

  • Komponenttimuokkaimessa muuta Options.ofFileMustExist totta. Tämä pakottaa dialogi sallii vain nykyiset tiedostot voidaan valita.

Hyvä niin. Nyt todella avata tiedoston, kun valikko on valittuna:

  • Lomakkeella klikkaa Game valikosta. Tämä avaa pudotusvalikon.
  • Klikkaa Load from file.... Odotetusti tämä luo .... tapahtumankäsittelijänä.
  • Lisää seuraava koodi tapahtumakäsittelijään:
  if OpenDialog1.Execute then
    ShowMessage(OpenDialog1.FileName);

Muista: edetä pienin askelin. Nyt on lisätty valikko lomakkeelle josta voi poistua sovelluksesta tai ladata pelilaudan pohjan levyltä. Vielä ei ole ??koodattu latausosaa, mutta yritetään ensin testata koodia joka on jo tehty.

  • Suorita sovellus.
  • Valitse valikosta Game/Load from file....
  • Valitse triangle.txt ja paina Avaa.
  • Ikkuna ponnahtaa joka osoittaa meille valitun tiedostonimen.
  • Lopeta ohjelma.

Nyt viimeistellään latauskoodi. Koodi on melko itsestään selvä ja käyttää uudelleen joitakin aliohjelmia jotka on jo luotu.

  • Mene lomakkeella ja klikkaa Game valikosta. Tämä avaa pudotusvalikon.
  • Klikkaa Load from file.... Odotetusti tämä avaa tapahtumankäsittelijän.
  • Korvaa koodin tällä:
procedure TfrmMain.MenuItem4Click(Sender: TObject);
begin
  // Open the pop up dialog
  if OpenDialog1.Execute then
  begin
    // Start with a new empty board
    StartNewGame;

    // Dynamically create a stringlist to load the board layout
    with TStringList.Create do
    begin
      // Load the board layout from the textfile
      LoadFromFile(OpenDialog1.FileName);

      // Initialize the board with the file's contents
      pegsol.InitializeBoard(Text);

      // Clean up the stringlist
      Free
    end;

    // After loading the new board update the paintbox
    pbPeg.Repaint;
  end;
end;

Ja se kaikki on siinä!

Suorita ohjelma, avaa pelilaudan pohjatiedosto ja pelaa peliä!


Silmänruokaa

Nyt on toimiva Peg Solitaire sovellus. Siellä on vielä paljon asioita parannettavaksi, mutta peli itsessään toimii hienosti. Viimeinen osa tätä opetusohjelmaa on parantaa pelin ulkonäköä. Toki peli toimii, mutta hieman näyttävämpi grafiikka olisi mukava. Ja se on melko helppo lisätä. Kaikki mitä tarvitaan on kolme kuvaa solutyyppejä varten, päivittää TPegSolPainter-luokkaa ja tärkein muoto, kerro maalari käyttää kuvia. Joten lisää kolme kuvaa TPegSolPainter luokkaan:

  • Avaa PegSolPainter käännösyksikkö.
  • Lisää kolme (3) kuvamuuttujaa private osioon:
  TPegSolPainter = class
  private
    PegSol     : TPegSolitaire;
    Canvas     : TCanvas;
    CellWidth  : integer;
    CellHeight : integer;

    ImgNoAccess: TPicture;
    ImgEmpty   : TPicture;
    ImgPeg     : TPicture;
  • Lisää kuvan latausaliohjelma public osioon.
  public
    constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);
    procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);
    function CanvasXYtoCell(const pX, pY: integer): TCellPosition;
    procedure LoadImage(const pCellType: TCellType; const pFilename: string);
  • Luo aliohjelman runko.
  • Lisää koodi:
procedure TPegSolPainter.LoadImage(const pCellType: TCellType; const pFilename: string);

  procedure UpdateImage(var pImg: TPicture; const pNewImage: TPicture);
  begin
    pImg.Free;
    pImg := pNewImage
  end;

var pic: TPicture;
begin
  // Make sure the file exists
  if FileExists(pFilename) then
  begin
    // First load the picture in a local variable
    pic := TPicture.Create;
    pic.LoadFromFile(pFilename);

    // Now update the required image based on the pCelltype parm
    case pCelltype of
      ctNoAccess: UpdateImage(ImgNoAccess, pic);
      ctEmpty   : UpdateImage(ImgEmpty, pic);
      ctPeg     : UpdateImage(ImgPeg, pic);
    end;
  end;
end;

Koodi on taas aika itsestään selvä. Huomaa, että käytetään lyhyttä paikallista aliohjelmaa päivittämään kuvaa. Jos emme tekisi sitä sitten case-lause ((case pCellType of) olisi monimutkaisempi (eli sisältäisi ylimääräistä koodia). Nyt kukin solutyyppi tarvitsee vain yhden rivin päivittäkseen oikean kuvan.

Nyt kun kuvia on lisätty luokkaan, täytyy käyttää niitä ja se tietenkin tapahtuu Repaint aliohjelmassa. Jokaista celltype tarkistetaan, onko kuva on saatavilla. Ja jos on niin käytetäään tavallisen piirustuksen sijasta kuvaa.

  • Paikallista Repaint aliohjelma.
  • Case lauseessa, muuta ctNoAccess, ctEmpty ja ctPeg osiot seuraavanlaiseksi:
      
// And now draw the cell based on the cell's contents
      case pegsol.Cell[iRow,iCol] of

        ctNoAccess: // Draw cells that are not accessible
          if not assigned(ImgNoAccess) then
            begin
              Canvas.Brush.Color := clGray;
              Canvas.Rectangle(CellArea);
            end
          else with ImgNoAccess do
            Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));


        ctEmpty:    // Draw cells that are currently empty
          if not assigned(ImgEmpty) then
            begin
              Canvas.Brush.Color := clBlue;
              Canvas.Rectangle(CellArea);
            end
          else with ImgEmpty do
            Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));


        ctPeg:      // Draw cells that are occupied
          if not assigned(ImgPeg) then
            begin
              Canvas.Brush.Color := clBlue;
              Canvas.Rectangle(CellArea);  // Erase the background first
              Canvas.Brush.Color := clGreen;
              Canvas.Ellipse(CellArea);
            end
          else with ImgPeg do
            Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));

      end;

Lisätään kuva latauskoodi piirtoluokkaan.

  • Avaa ufrmmain -käännösyksikkö.
  • Paikallista aliohjelma StartNewGame.
  • Lisää LoadImage kutsu pegpaint olion luomisen jälkeen:
  // Start with a new 7x7 game
  pegsol := TPegSolitaire.Create(7);
  pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);

  pegpaint.LoadImage(ctNoAccess, 'tutpeg_cellforbidden.jpg');
  pegpaint.LoadImage(ctEmpty,    'tutpeg_cellempty.jpg');
  pegpaint.LoadImage(ctPeg,      'tutpeg_celloccupied.jpg');

Varmista, että kuvat ladataan ja laitetaan ohjelman bin kansioon, tai vielä parempaa, luot omat kuvat. Huom. kun lataat allaolevia kuvia, klikkaa niitä ensin.


tutpeg cellforbidden.jpg tutpeg cellempty.jpg tutpeg celloccupied.jpg

  • Nyt kun testataan Peg Solitaire peliä. Toivottavasti se näyttää suunnilleen samanlaiselta kuin kuva tämän opetusohjelman alussa ...


Viimeinen "helmi"

Useimmilla ohjelmilla on oma kuvake, joka näkyvät valikon ja tiedoston selaus ikkunoissa. On helppo lisätä oma kuvake sovellukseen.

  • Valitse valikosta Projekti / Projektikohtaiset asetukset ....
  • Klikkaa Projektin asetukset/ Sovellus. Tämä näyttää ikkunassa sovelluksen kuvaketta.
  • Klikkaa Lataa kuvake ja lataa siihen sopiva kuvake tai ladata marmorikuvan joka on tässä alla:

tutpeg marble.png

  • Paina OK.

Sovelluksella on nyt oma kuvake, joka näkyy valikossa ja tiedoston selaus ikkunassa.