Peg Solitaire tutorial/fi

From Lazarus wiki
Jump to: navigation, search

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ä 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:

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

  • Pains OK.

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