Peg Solitaire tutorial/fi: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Peg Solitaire tutorial/fi
| Old | New | Line | |
|---|---|---|---|
| 1 | + | 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](http://en.wikipedia.org/wiki/Peg_solitaire)). Jos kaikki menee hyvin niin lopulta se näyttää tältä: | |
| 2 | + | ||
| 3 | + | <figure> | |
| 4 | + | <img src="tutpeg_solitaire.png" title="tutpeg_solitaire.png" /> | |
| 5 | + | <figcaption>tutpeg_solitaire.png</figcaption> | |
| 6 | + | </figure> | |
| 7 | + | ||
| 8 | + | ## Projektin aloitus | |
| 9 | + | ||
| 10 | + | Kuten edellisessä opetusohjelmassakin niin on parasta aloittaa puhtaalta pöydältä. Tee erillinen hakemisto(tai kansio) kutakin projektia kohti. Nopea kertaus: | |
| 11 | + | ||
| 12 | + | - Luo uusi hakemisto tähän peliin. | |
| 13 | + | - Aloita uusi projekti (Projekti / Uusi projekti ... ja valitse Sovellus). | |
| 14 | + | - Tallenna projekti nimellä *PegSolitaire*. | |
| 15 | + | - Tallenna aloitusikkuna/lomake nimellä *ufrmMain*. | |
| 16 | + | - Komponenttimuokkaimessa muuta lomakkeen nimeksi *frmMain*. | |
| 17 | + | - Muuta Caption arvoon *Lazarus Peg Solitaire*. | |
| 18 | + | - Valitse valikosta: Projekti / Projektikohtaiset asetukset ... | |
| 19 | + | - Valitse Kääntäjän asetukset / Hakupolut (klikkaa solmun puunäkymää) | |
| 20 | + | - Kirjoita teksti bin\\ ennen kohdetiedostonimeä (tai bin/ esim. unix/linux ympäristössä) | |
| 21 | + | ||
| 22 | + | Ja lisänä tässä projektissa: | |
| 23 | + | ||
| 24 | + | - Avaa projekti valintaikkunan (<span class="kbd">Shift</span>+<span class="kbd">Ctrl</span>+<span class="kbd">F11</span>). | |
| 25 | + | - Valitse kääntäjän asetukset / Virheenjäljitys. | |
| 26 | + | - Ota käyttöön ylivuoto- ja aluevirhe tarkistus (katso kuva alla). | |
| 27 | + | ||
| 28 | + | <figure> | |
| 29 | + | <img src="tutpeg_compiler_options.png" title="tutpeg_compiler_options.png" /> | |
| 30 | + | <figcaption>tutpeg_compiler_options.png</figcaption> | |
| 31 | + | </figure> | |
| 32 | + | ||
| 33 | + | ## Ensiaskeleet | |
| 34 | + | ||
| 35 | + | 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. | |
| 36 | + | ||
| 37 | + | - Valitse valikosta *Tiedosto / Uusi käännösyksikkö*. | |
| 38 | + | - Tallenna käännösyksikkö nimellä *PegDatastructures.pas* | |
| 39 | + | ||
| 40 | + | 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. | |
| 41 | + | ||
| 42 | + | - Lisää seuraava koodi PegDatastructures käännösyksikköön ( *[[Uses/fi|uses]]*-lausekkeen jälkeen juuri ennen *[[Implementation/fi|implementation]]* osiota): | |
| 43 | + | ||
| 44 | + | ``` pascal | |
| 45 | + | const | |
| 46 | + | C_MAX = 7; // Max board size: 7x7 | |
| 47 | + | ||
| 48 | + | type | |
| 49 | + | TCellNums = 1..C_MAX; | |
| 50 | + | TCellType = (ctNoAccess, ctEmpty, ctPeg); | |
| 51 | + | TPegCells = array[TCellNums, TCellNums] of TCellType; | |
| 52 | + | ||
| 53 | + | TPegSolitaire = class | |
| 54 | + | private | |
| 55 | + | Size: TCellNums; | |
| 56 | + | PegCells: TPegCells; | |
| 57 | + | ||
| 58 | + | public | |
| 59 | + | constructor Create(const pSize: TCellNums); | |
| 60 | + | end; | |
| 61 | + | ``` | |
| 62 | + | ||
| 63 | + | 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: | |
| 64 | + | ||
| 65 | + | ``` pascal | |
| 66 | + | property Cell[const pRow, pCol: TCellNums]: TCellType; | |
| 67 | + | ``` | |
| 68 | + | ||
| 69 | + | - Sijoita tekstikursori rakentajan eli constructor:n riville. | |
| 70 | + | - Paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>: 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. | |
| 71 | + | - Funktio GetCell noutaa tiedot PegCells taulukosta. Lisää seuraava koodi tähän funktioon: | |
| 72 | + | ||
| 73 | + | ``` pascal | |
| 74 | + | result := PegCells[pRow,pCol]; | |
| 75 | + | ``` | |
| 76 | + | ||
| 77 | + | - Aliohjelma SetCell sijoittaa dataa PegCells taulukkoon. Lisää seuraava koodi tähän aliohjelmaan: | |
| 78 | + | ||
| 79 | + | ``` pascal | |
| 80 | + | PegCells[pRow,pCol] := AValue; | |
| 81 | + | ``` | |
| 82 | + | ||
| 83 | + | - Ja viimeistellä rakentaja Create. Lisää tämä koodi sen runkoon: | |
| 84 | + | ||
| 85 | + | ``` pascal | |
| 86 | + | var iRow,iCol: integer; | |
| 87 | + | begin | |
| 88 | + | // Store the size of the board locally | |
| 89 | + | Size := pSize; | |
| 90 | + | ||
| 91 | + | // Initialize all cells to 'not accessible' | |
| 92 | + | for iRow := 1 to C_MAX do | |
| 93 | + | for iCol := 1 to C_MAX do | |
| 94 | + | Cell[iRow,iCol] := ctNoAccess; | |
| 95 | + | ``` | |
| 96 | + | ||
| 97 | + | 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. | |
| 98 | + | ||
| 99 | + | Ohjelman ikkunalomake aikoo käyttää datarakennetta joka on määritelty PegDatastructure käännösyksikköön. | |
| 100 | + | ||
| 101 | + | - Siirry lähdekoodieditorissa ufrmmain-käännösyksikköön. | |
| 102 | + | - Lisää PegDatastructures sen uses luetteloon joka on tiedoston yläosassa: | |
| 103 | + | ||
| 104 | + | ``` pascal | |
| 105 | + | uses | |
| 106 | + | PegDatastructures, | |
| 107 | + | Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs; | |
| 108 | + | ``` | |
| 109 | + | ||
| 110 | + | - Paina <span class="kbd">F12</span> (tämä tuo esiin lomakkeen). | |
| 111 | + | - Komponenttipaletin [[Standard tab/fi|Standard]]-välilehdeltä valitaan [[TButton/fi|TButton]] ja pudota se lomakkeelle vasempaan yläkulmaan. | |
| 112 | + | - Vaihda sen Caption ominaisuuten teksti *Test paint*. | |
| 113 | + | - Komponenttipaletin [[Additional tab/fi|Additional]]-välilehdeltä valitaan TPaintbox-komponentti ja tuodaan se lomakkeelle. | |
| 114 | + | - Muuta sen Align arvoon *alRight*. | |
| 115 | + | - Muuta BordSpacing.Around arvoon *4*. | |
| 116 | + | - Muuta Anchors.akLeft arvoon *true*. | |
| 117 | + | - Kirjoita Name-ominaisuuteen teksti: *pbPeg*. | |
| 118 | + | - Muuta lomakkeen kokoa, jotta se näyttää suunnilleen tältä: | |
| 119 | + | ||
| 120 | + | <figure> | |
| 121 | + | <img src="tutpeg_empty.png" title="tutpeg_empty.png" /> | |
| 122 | + | <figcaption>tutpeg_empty.png</figcaption> | |
| 123 | + | </figure> | |
| 124 | + | ||
| 125 | + | 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. | |
| 126 | + | ||
| 127 | + | - Tuplaklikkaa *Test paint*-painiketta (tämä luo tapahtumakäsittelijän). | |
| 128 | + | - Lisää kaksi muuttujaa: | |
| 129 | + | ||
| 130 | + | ``` pascal | |
| 131 | + | var | |
| 132 | + | CellWidth : integer; | |
| 133 | + | CellHeight: integer; | |
| 134 | + | ``` | |
| 135 | + | ||
| 136 | + | Tarvitaan ylimääräisiä muuttujia pitämään välituloksia: | |
| 137 | + | ||
| 138 | + | - Lisää kolme paikallista muuttujaa: | |
| 139 | + | ||
| 140 | + | ``` pascal | |
| 141 | + | iRow, iCol: TCellNums; | |
| 142 | + | CellArea : TRect; | |
| 143 | + | ``` | |
| 144 | + | ||
| 145 | + | CellArea käytetään rajoittamaan suorakulmaista aluetta näytöllä, johon solu piirretään. | |
| 146 | + | ||
| 147 | + | Jotta käsittellään kaikki rivit ja sarakkeet niin tehdään se kahdella suoraviivaisella [[For/fi|for]]-silmukalla. | |
| 148 | + | ||
| 149 | + | - Lisää seuraava koodi tapahtumankäsittelijään: | |
| 150 | + | ||
| 151 | + | ``` pascal | |
| 152 | + | ||
| 153 | + | // Calculate the width/height of each cell to accomodate for all cells in the paintbox | |
| 154 | + | CellWidth := pbPeg.Width div 7; | |
| 155 | + | CellHeight := pbPeg.Height div 7; | |
| 156 | + | ||
| 157 | + | // Draw boxes for all cells | |
| 158 | + | for iRow := 1 to 7 do | |
| 159 | + | for iCol := 1 to 7 do | |
| 160 | + | begin | |
| 161 | + | // Calculate the position of the cell in the paintbox | |
| 162 | + | CellArea.Top := (iRow-1) * CellHeight; | |
| 163 | + | CellArea.Left := (iCol-1) * CellWidth; | |
| 164 | + | CellArea.Right := CellArea.Left + CellWidth; | |
| 165 | + | CellArea.Bottom := CellArea.Top + CellHeight; | |
| 166 | + | // And now draw the cell | |
| 167 | + | pbPeg.Canvas.Rectangle(CellArea); | |
| 168 | + | end; | |
| 169 | + | ``` | |
| 170 | + | ||
| 171 | + | 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ä. | |
| 172 | + | ||
| 173 | + | - Kääntää ja ajaa ohjelma (paina <span class="kbd">F9</span>). | |
| 174 | + | - Paina *Test paint*-painiketta. | |
| 175 | + | - Suurenna lomaketta (solut häviävät mutta älä huoli se korjatuu). | |
| 176 | + | - Paina *Test paint*-painiketta uudelleen | |
| 177 | + | ||
| 178 | + | 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. | |
| 179 | + | ||
| 180 | + | - Luo uusi käännösyksikkö (Tiedosto / Uusi käännösyksikkö). | |
| 181 | + | - Tallenna se nimellä PegSolPainter.pas (Tiedosto / Tallenna nimellä ...). | |
| 182 | + | - Lisää uusi luokka tähän käännösyksikköön, se tekee kaiken piirtämisen (luokka tulee uses-lauseen jälkeen, ennen | |
| 183 | + | ||
| 184 | + | implementation osiota). | |
| 185 | + | ||
| 186 | + | ``` pascal | |
| 187 | + | type | |
| 188 | + | TPegSolPainter = class | |
| 189 | + | private | |
| 190 | + | PegSol : TPegSolitaire; | |
| 191 | + | Canvas : TCanvas; | |
| 192 | + | public | |
| 193 | + | constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas); | |
| 194 | + | end; | |
| 195 | + | ``` | |
| 196 | + | ||
| 197 | + | Huomaa, että TPegSolitaire muuttuja lisätään myös, koska käyttää tätä luokkaa käytetään noutamaan solujen tila. | |
| 198 | + | ||
| 199 | + | - Sijoita tekstikursorin rakentajan eli constructor:n riville ja paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>. | |
| 200 | + | - Rakentajan kaksi alustus parametriä on sijoitettava luokan private muuttujiin (Täydennä luokan rakentajaa siten että se näyttää tältä) : | |
| 201 | + | ||
| 202 | + | ``` pascal | |
| 203 | + | constructor TPegSolPainter.Create(pPegSol: TPegSolitaire; pCanvas: TCanvas); | |
| 204 | + | begin | |
| 205 | + | PegSol := pPegSol; | |
| 206 | + | Canvas := pCanvas; | |
| 207 | + | end; | |
| 208 | + | ``` | |
| 209 | + | ||
| 210 | + | 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. | |
| 211 | + | ||
| 212 | + | - Lisää *PegDatastructures* ja *Graphics* *uses* lauseen luetteloon. | |
| 213 | + | ||
| 214 | + | ``` pascal | |
| 215 | + | uses | |
| 216 | + | PegDatastructures, | |
| 217 | + | Graphics, | |
| 218 | + | Classes, SysUtils; | |
| 219 | + | ``` | |
| 220 | + | ||
| 221 | + | 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. | |
| 222 | + | ||
| 223 | + | Nyt tiedetään tämä, voidaan lisätä piirustusmetodi luokkaan. | |
| 224 | + | ||
| 225 | + | - Lisää aliohjelma Repaint luokkaan (jolloin luokka määrittely näyttää tältä). | |
| 226 | + | ||
| 227 | + | ``` pascal | |
| 228 | + | ||
| 229 | + | TPegSolPainter = class | |
| 230 | + | private | |
| 231 | + | PegSol : TPegSolitaire; | |
| 232 | + | Canvas : TCanvas; | |
| 233 | + | ||
| 234 | + | public | |
| 235 | + | constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas); | |
| 236 | + | procedure Repaint(const pCanvasWidth, pCanvasHeight: integer); | |
| 237 | + | end; | |
| 238 | + | </sourcet> | |
| 239 | + | * Luo aliohjelman Repaint runko. Paina <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>C</kbd>. | |
| 240 | + | * Kopio koodi ''TfrmMain.Button1Click(Sender: TObject)''-aliohjelmasta tähän runkoon (Jolloin siitä tulee tälläinen) | |
| 241 | + | <syntaxhighlight lang="pascal"> | |
| 242 | + | procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer); | |
| 243 | + | var | |
| 244 | + | CellWidth : integer; | |
| 245 | + | CellHeight : integer; | |
| 246 | + | iRow, iCol : TCellNums; | |
| 247 | + | CellArea : TRect; | |
| 248 | + | begin | |
| 249 | + | // Calculate the width of each cell to accomodate for all cells | |
| 250 | + | CellWidth := pbPeg.Width div 7; | |
| 251 | + | CellHeight := pbPeg.Height div 7; | |
| 252 | + | ||
| 253 | + | // Draw boxes for all cells | |
| 254 | + | for iRow := 1 to 7 do | |
| 255 | + | for iCol := 1 to 7 do | |
| 256 | + | begin | |
| 257 | + | // Calculate the position of the cell in the paintbox | |
| 258 | + | CellArea.Top := (iRow-1) * CellHeight; | |
| 259 | + | CellArea.Left := (iCol-1) * CellWidth; | |
| 260 | + | CellArea.Right := CellArea.Left + CellWidth; | |
| 261 | + | CellArea.Bottom := CellArea.Top + CellHeight; | |
| 262 | + | // And now draw the cell | |
| 263 | + | pbPeg.Canvas.Rectangle(CellArea); | |
| 264 | + | end; | |
| 265 | + | end; | |
| 266 | + | ``` | |
| 267 | + | ||
| 268 | + | Koska ei enää tarvita TPaintbox-komponenttia pbPeg. Niin on poistettava viittaukset siihen. Joten kolmessa kohdassa tarvitaan muutoksia: | |
| 269 | + | ||
| 270 | + | - Muutos CellWidth:n ja CellHeight:n laskennassa: | |
| 271 | + | ||
| 272 | + | ``` pascal | |
| 273 | + | CellWidth := pCanvasWidth div 7; | |
| 274 | + | CellHeight := pCanvasHeight div 7; | |
| 275 | + | ``` | |
| 276 | + | ||
| 277 | + | - Muutos suorakulmion piirtämisessä: | |
| 278 | + | ||
| 279 | + | ``` pascal | |
| 280 | + | Canvas.Rectangle(CellArea); | |
| 281 | + | ``` | |
| 282 | + | ||
| 283 | + | 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. | |
| 284 | + | ||
| 285 | + | - Käännösyksikössä *ufrmmain.pas*, etsi Button1Click aliohjelma. | |
| 286 | + | - Poista siitä kaikki lausekkeet ja muuttujat (Tyhjennä se). | |
| 287 | + | - Lisätään kaksi uutta muuttujaa: peliluokan ja piirtämisluokan (Kaikkien näiden toimien tuloksena aliohjelma nyt näyttää tältä): | |
| 288 | + | ||
| 289 | + | ``` pascal | |
| 290 | + | procedure TfrmMain.Button1Click(Sender: TObject); | |
| 291 | + | var | |
| 292 | + | pegsol : TPegSolitaire; // The game data | |
| 293 | + | pegpaint: TPegSolPainter; // The paint class for the game | |
| 294 | + | begin | |
| 295 | + | end; | |
| 296 | + | ``` | |
| 297 | + | ||
| 298 | + | - Lisää *PegSolPainter* uses-lauseen luetteloon. | |
| 299 | + | ||
| 300 | + | Piirtoluokan käyttö on yksinkertaista: Luodaan uusi esiintymä ja kutsutaan piirto aliohjelmaa: | |
| 301 | + | ||
| 302 | + | - Lisää seuraava koodi Button1Click aliohjelmaan: | |
| 303 | + | ||
| 304 | + | ``` pascal | |
| 305 | + | ||
| 306 | + | // Create a new game object | |
| 307 | + | pegsol := TPegSolitaire.Create(7); | |
| 308 | + | ||
| 309 | + | // Create a new painter object to paint this game | |
| 310 | + | pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas); | |
| 311 | + | ||
| 312 | + | // And paint the board | |
| 313 | + | pegpaint.Repaint(pbPeg.Width, pbPeg.Height); | |
| 314 | + | ||
| 315 | + | // Clean up | |
| 316 | + | pegpaint.Free; | |
| 317 | + | pegsol.Free | |
| 318 | + | ``` | |
| 319 | + | ||
| 320 | + | Suorita ja testta ohjelma. Nähdään että se toimii samaan tapaan kuin ennen. Tulos näyttää tältä:  | |
| 321 | + | ||
| 322 | + | ## Kyse tapahtumista | |
| 323 | + | ||
| 324 | + | 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! | |
| 325 | + | ||
| 326 | + | 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. | |
| 327 | + | ||
| 328 | + | - Mene lomakkeelle (Valitse ufrmMain lähdekoodieditorissa ja paina <span class="kbd">F12</span>). | |
| 329 | + | - Valitse TPaintbox pbPeg. | |
| 330 | + | - Siirry *tapahtumat*-välilehdelle komponenttimuokkaimessa. | |
| 331 | + | - Yksi luetelluista tapahtumista on OnPaint. | |
| 332 | + | ||
| 333 | + | <figure> | |
| 334 | + | <img src="tutpeg_onpaint.png" title="tutpeg_onpaint.png" /> | |
| 335 | + | <figcaption>tutpeg_onpaint.png</figcaption> | |
| 336 | + | </figure> | |
| 337 | + | ||
| 338 | + | Tätä tapahtumaa kutsutaan joka kerta kun TPaintbox tarvitsee uudelleenpiirtämistä. Se on paikka johon tehdään piirtäminen. | |
| 339 | + | ||
| 340 | + | - Valitse OnPaint tapahtuma komponenttimuokkaimessa ja klikkaa pientä (...) painiketta kolmella pisteellä . Tämä luo tapahtumakäsittelijän rungon. | |
| 341 | + | - Kopioi / Liitä tarkka koodi *Button1Click* tapahtumakäsittelystä tähän uuteen aliohjelmaan. | |
| 342 | + | ||
| 343 | + | ``` pascal | |
| 344 | + | procedure TfrmMain.pbPegPaint(Sender: TObject); | |
| 345 | + | var | |
| 346 | + | pegsol : TPegSolitaire; // The game data | |
| 347 | + | pegpaint: TPegSolPainter; // The paint class for the game | |
| 348 | + | begin | |
| 349 | + | // Create a new game object | |
| 350 | + | pegsol := TPegSolitaire.Create(7); | |
| 351 | + | ||
| 352 | + | // Create a new painter object to paint this game | |
| 353 | + | pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas); | |
| 354 | + | ||
| 355 | + | // And paint the board | |
| 356 | + | pegpaint.Repaint(pbPeg.Width, pbPeg.Height); | |
| 357 | + | ||
| 358 | + | // Clean up | |
| 359 | + | pegpaint.Free; | |
| 360 | + | pegsol.Free | |
| 361 | + | end; | |
| 362 | + | ``` | |
| 363 | + | ||
| 364 | + | - Poista kaikki koodi ja muuttujat aliohjelmasta Button1Click. Muista: Ohjelmankehitysympäristö automaattisesti poistaa tämän tyhjän aliohjelman. | |
| 365 | + | - Poista *Test paint* -painike lomakkeelta. | |
| 366 | + | - Suorita ohjelma, katso mitä tapahtuu. | |
| 367 | + | ||
| 368 | + | 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. | |
| 369 | + | ||
| 370 | + | - Siirry lomakkeen määrittelyyn. | |
| 371 | + | - Lisää kaksi muuttujaa jotka luotiin OnPaint tapahtumaan: | |
| 372 | + | ||
| 373 | + | ``` pascal | |
| 374 | + | TfrmMain = class(TForm) | |
| 375 | + | pbPeg: TPaintBox; | |
| 376 | + | procedure pbPegPaint(Sender: TObject); | |
| 377 | + | private | |
| 378 | + | { private declarations } | |
| 379 | + | pegsol : TPegSolitaire; // The game data | |
| 380 | + | pegpaint: TPegSolPainter; // The paint class for the game | |
| 381 | + | public | |
| 382 | + | { public declarations } | |
| 383 | + | end; | |
| 384 | + | ``` | |
| 385 | + | ||
| 386 | + | 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. | |
| 387 | + | ||
| 388 | + | - Lisää aliohjelma StartNewGame lomakkeelle. | |
| 389 | + | ||
| 390 | + | ``` pascal | |
| 391 | + | ||
| 392 | + | private | |
| 393 | + | { private declarations } | |
| 394 | + | pegsol : TPegSolitaire; // The game data | |
| 395 | + | pegpaint: TPegSolPainter; // The paint class for the game | |
| 396 | + | ||
| 397 | + | procedure StartNewGame; | |
| 398 | + | ``` | |
| 399 | + | ||
| 400 | + | - Luo aliohjelman runko. Paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>. | |
| 401 | + | - Lisää alustus koodi: | |
| 402 | + | ||
| 403 | + | ``` pascal | |
| 404 | + | procedure TfrmMain.StartNewGame; | |
| 405 | + | begin | |
| 406 | + | // Clean up the previous game | |
| 407 | + | pegpaint.Free; | |
| 408 | + | pegsol.Free; | |
| 409 | + | ||
| 410 | + | // Start with a new game | |
| 411 | + | pegsol := TPegSolitaire.Create(7); | |
| 412 | + | pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas); | |
| 413 | + | end; | |
| 414 | + | ``` | |
| 415 | + | ||
| 416 | + | 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. | |
| 417 | + | ||
| 418 | + | - Mene lomakkeelle (Lähdekoodieditorissa paina <span class="kbd">F12</span>). | |
| 419 | + | - Tuplaklikkaa jossain vapaata aluetta. Älä klikkaa TPaintbox:a. | |
| 420 | + | ||
| 421 | + | <figure> | |
| 422 | + | <img src="tutpeg_form_create.png" title="tutpeg_form_create.png" /> | |
| 423 | + | <figcaption>tutpeg_form_create.png</figcaption> | |
| 424 | + | </figure> | |
| 425 | + | ||
| 426 | + | - Lisää sen runkoon aliohjelman StartNewGame kutsu: | |
| 427 | + | ||
| 428 | + | ``` pascal | |
| 429 | + | procedure TfrmMain.FormCreate(Sender: TObject); | |
| 430 | + | begin | |
| 431 | + | StartNewGame; | |
| 432 | + | end; | |
| 433 | + | ``` | |
| 434 | + | ||
| 435 | + | Nyt kun peli- ja piirto-oliot luodaan ohjelman alkaessa niitä ei enää tarvita OnPaint aliohjelmassa. | |
| 436 | + | ||
| 437 | + | - Paikallista aliohjelma procedure TfrmMain.pbPegPaint (Sender: TObject); | |
| 438 | + | - Poista paikalliset muuttujat ja kaikki koodi paitsi rivin joka tekee piirtämisen. (jolloin se on näin yksinkertainen) | |
| 439 | + | ||
| 440 | + | ``` pascal | |
| 441 | + | procedure TfrmMain.pbPegPaint(Sender: TObject); | |
| 442 | + | begin | |
| 443 | + | // Paint the board | |
| 444 | + | pegpaint.Repaint(pbPeg.Width, pbPeg.Height); | |
| 445 | + | end; | |
| 446 | + | ``` | |
| 447 | + | ||
| 448 | + | - Suorita ohjelma ja katso mitä tapahtuu. | |
| 449 | + | ||
| 450 | + | ## Intermezzo | |
| 451 | + | ||
| 452 | + | 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ä. | |
| 453 | + | ||
| 454 | + | - Mene käännösyksikköön ufrmmain.pas ja siellä aliohjelmaan StartNewGame. | |
| 455 | + | - Muuta rivi `pegsol: = TPegSolitaire.Create (7);` riviksi `pegsol: = TPegSolitaire.Create (5);`. Joten pienempi lauta 5x5 neliöitä on luotu. | |
| 456 | + | - Suorita ohjelma ja katso mitä tapahtuu. | |
| 457 | + | ||
| 458 | + | 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> ). | |
| 459 | + | ||
| 460 | + | Taikanumeroiden käyttäminen on suuri katastrofi: se on asia, kun, ei jos, ohjelma epäonnistuu. | |
| 461 | + | ||
| 462 | + | - Avaa käännösyksikkö PegSolPainter. | |
| 463 | + | - Paikanna aliohjelma *TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);*. | |
| 464 | + | ||
| 465 | + | 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: | |
| 466 | + | ||
| 467 | + | ``` pascal | |
| 468 | + | ||
| 469 | + | // Calculate the width of each cell to accomodate for all cells | |
| 470 | + | CellWidth := pCanvasWidth div pegsol.Size; | |
| 471 | + | CellHeight := pCanvasHeight div pegsol.Size; | |
| 472 | + | ||
| 473 | + | // Draw boxes for all cells | |
| 474 | + | for iRow := 1 to pegsol.Size do | |
| 475 | + | for iCol := 1 to pegsol.Size do | |
| 476 | + | begin | |
| 477 | + | // Calculate the position of the cell in the paintbox | |
| 478 | + | CellArea.Top := (iRow-1) * CellHeight; | |
| 479 | + | CellArea.Left := (iCol-1) * CellWidth; | |
| 480 | + | CellArea.Right := CellArea.Left + CellWidth; | |
| 481 | + | CellArea.Bottom := CellArea.Top + CellHeight; | |
| 482 | + | // And now draw the cell | |
| 483 | + | Canvas.Rectangle(CellArea); | |
| 484 | + | end; | |
| 485 | + | ``` | |
| 486 | + | ||
| 487 | + | 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. | |
| 488 | + | ||
| 489 | + | - Mene käännösyksikköön PegDatastructures. | |
| 490 | + | - Uudelleen nimeä luokan TPegSolitaire yksityisen muuttujan Size uudeksi nimeksi *FSize*. | |
| 491 | + | - Lisää julkinen vain luku ominaisuus luokkaan: property Size: TCellNums read FSize; | |
| 492 | + | ||
| 493 | + | Luokka nyt näyttää tältä: | |
| 494 | + | ||
| 495 | + | ``` pascal | |
| 496 | + | ||
| 497 | + | TPegSolitaire = class | |
| 498 | + | private | |
| 499 | + | FSize: TCellNums; | |
| 500 | + | PegCells: TPegCells; | |
| 501 | + | function GetCell(const pRow, pCol: TCellNums): TCellType; | |
| 502 | + | procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType); | |
| 503 | + | ||
| 504 | + | public | |
| 505 | + | constructor Create(const pSize: TCellNums); | |
| 506 | + | property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell; | |
| 507 | + | property Size: TCellNums read FSize; | |
| 508 | + | end; | |
| 509 | + | ``` | |
| 510 | + | ||
| 511 | + | 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. | |
| 512 | + | ||
| 513 | + | - Paikallista rakentaja (constructor). | |
| 514 | + | - Muuta rivi*Size := pSize;* riviksi *FSize := pSize;* (Jotta ei saada käännösvirhettä). | |
| 515 | + | - 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): | |
| 516 | + | ||
| 517 | + | ``` pascal | |
| 518 | + | constructor TPegSolitaire.Create(const pSize: TCellNums); | |
| 519 | + | var iRow,iCol: integer; | |
| 520 | + | begin | |
| 521 | + | FSize := pSize; | |
| 522 | + | for iRow := 1 to Size do | |
| 523 | + | for iCol := 1 to Size do | |
| 524 | + | Cell[iRow,iCol] := ctNoAccess; | |
| 525 | + | end; | |
| 526 | + | ``` | |
| 527 | + | ||
| 528 | + | Olemme nyt valmis testaamaan ohjelman ja katso jos se piirtää nyt mukavan 5x5 matriisi. | |
| 529 | + | ||
| 530 | + | - Suorita ohjelma ja katso mitä tapahtuu (5x5 matriisi piirretään). | |
| 531 | + | ||
| 532 | + | Nyt kun pelin perusteet ovat paikoillaan, on aika täsmentää käyttöliittymää. | |
| 533 | + | ||
| 534 | + | ## Ollaan taiteellisia | |
| 535 | + | ||
| 536 | + | 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. | |
| 537 | + | ||
| 538 | + | - Avaa lähdekoodieditorissa käännösyksikkö PegSolPainter (käännösyksikkö joka piirtää pelilaudan). | |
| 539 | + | - Siirry Repaint aliohjelmaan. | |
| 540 | + | - Etsi rivi, jossa solu on piirretään: *Canvas.Rectangle (CellArea);*. | |
| 541 | + | ||
| 542 | + | 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. | |
| 543 | + | ||
| 544 | + | - Muuta solujen piirtämistä: | |
| 545 | + | ||
| 546 | + | ``` pascal | |
| 547 | + | // Draw boxes for all cells | |
| 548 | + | for iRow := 1 to pegsol.Size do | |
| 549 | + | for iCol := 1 to pegsol.Size do | |
| 550 | + | begin | |
| 551 | + | // Calculate the position of the cell in the paintbox | |
| 552 | + | CellArea.Top := (iRow-1) * CellHeight; | |
| 553 | + | CellArea.Left := (iCol-1) * CellWidth; | |
| 554 | + | CellArea.Right := CellArea.Left + CellWidth; | |
| 555 | + | CellArea.Bottom := CellArea.Top + CellHeight; | |
| 556 | + | ||
| 557 | + | // And now draw the cell based on the cell's contents | |
| 558 | + | case pegsol.Cell[iRow,iCol] of | |
| 559 | + | ||
| 560 | + | ctNoAccess: // Draw cells that are not accessible | |
| 561 | + | begin | |
| 562 | + | Canvas.Brush.Color := clGray; | |
| 563 | + | Canvas.Rectangle(CellArea); | |
| 564 | + | end; | |
| 565 | + | ||
| 566 | + | ctEmpty: // Draw cells that are currently empty | |
| 567 | + | begin | |
| 568 | + | Canvas.Brush.Color := clBlue; | |
| 569 | + | Canvas.Rectangle(CellArea); | |
| 570 | + | end; | |
| 571 | + | ||
| 572 | + | ctPeg: // Draw cells that are occupied | |
| 573 | + | begin | |
| 574 | + | Canvas.Brush.Color := clBlue; | |
| 575 | + | Canvas.Rectangle(CellArea); // Erase the background first | |
| 576 | + | Canvas.Brush.Color := clGreen; | |
| 577 | + | Canvas.Ellipse(CellArea); // Draw the pegs as green circles | |
| 578 | + | end; | |
| 579 | + | end; | |
| 580 | + | end; | |
| 581 | + | ``` | |
| 582 | + | ||
| 583 | + | 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. | |
| 584 | + | ||
| 585 | + | - Avaa ufrmmain käännösyksikkö. | |
| 586 | + | - Paikallista StartNewGame aliohjelma. | |
| 587 | + | - Luo 5x5 pelin sijasta 7x7 peli. | |
| 588 | + | - Alusta muutamia soluja. Esimerkiksi: | |
| 589 | + | ||
| 590 | + | ``` pascal | |
| 591 | + | procedure TfrmMain.StartNewGame; | |
| 592 | + | begin | |
| 593 | + | // Clean up the previous game | |
| 594 | + | pegpaint.Free; | |
| 595 | + | pegsol.Free; | |
| 596 | + | ||
| 597 | + | // Start with a new 7x7 game | |
| 598 | + | pegsol := TPegSolitaire.Create(7); | |
| 599 | + | pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas); | |
| 600 | + | ||
| 601 | + | // Initialize some cells | |
| 602 | + | pegsol.Cell[3,4] := ctEmpty; | |
| 603 | + | pegsol.Cell[4,2] := ctPeg; | |
| 604 | + | pegsol.Cell[4,3] := ctPeg; | |
| 605 | + | pegsol.Cell[4,4] := ctEmpty; | |
| 606 | + | pegsol.Cell[4,5] := ctPeg; | |
| 607 | + | pegsol.Cell[4,6] := ctPeg; | |
| 608 | + | pegsol.Cell[5,4] := ctEmpty; | |
| 609 | + | end; | |
| 610 | + | ``` | |
| 611 | + | ||
| 612 | + | - Suorita ohjelma (sen tulee näyttää jokseenkin samanlaiselta kuin kuva alla). | |
| 613 | + | ||
| 614 | + | <figure> | |
| 615 | + | <img src="tutpeg_first_pegs.png" title="tutpeg_first_pegs.png" /> | |
| 616 | + | <figcaption>tutpeg_first_pegs.png</figcaption> | |
| 617 | + | </figure> | |
| 618 | + | ||
| 619 | + | ## Täytä pelilauta | |
| 620 | + | ||
| 621 | + | Kuten tiedetään niin klassinen solitaire pelilaudan pitäisi näyttää tältä: | |
| 622 | + | ||
| 623 | + | <figure> | |
| 624 | + | <img src="tutpeg_classic.png" title="tutpeg_classic.png" /> | |
| 625 | + | <figcaption>tutpeg_classic.png</figcaption> | |
| 626 | + | </figure> | |
| 627 | + | ||
| 628 | + | 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: | |
| 629 | + | ||
| 630 | + | ``` pascal | |
| 631 | + | ||
| 632 | + | // Initialize the cells to the classic game | |
| 633 | + | pegsol.InitializeBoard( ' ooo ' + LineEnding + | |
| 634 | + | ' ooo ' + LineEnding + | |
| 635 | + | 'ooooooo' + LineEnding + | |
| 636 | + | 'ooo.ooo' + LineEnding + | |
| 637 | + | 'ooooooo' + LineEnding + | |
| 638 | + | ' ooo ' + LineEnding + | |
| 639 | + | ' ooo ' ); | |
| 640 | + | ``` | |
| 641 | + | ||
| 642 | + | 1. **o** on solu jossa on pelinappula. | |
| 643 | + | 2. **.** on tyhjä, mutta pelattavaa solu. | |
| 644 | + | 3. Välilyönnit osoittavat solut, jotka eivät ole käytettävissä. | |
| 645 | + | ||
| 646 | + | Oletetaan että tämä tulee toimimaan ja luodaan aliohjelma TPegSolitaire luokkaan joka voi hoitaa tämän. | |
| 647 | + | ||
| 648 | + | - Ollaan optimistinen (kutsutaan myös *Top down* suunnitteluksi) ja lisätään edellä ollut koodi TfrmMain.StartNewGame: | |
| 649 | + | ||
| 650 | + | ``` pascal | |
| 651 | + | procedure TfrmMain.StartNewGame; | |
| 652 | + | begin | |
| 653 | + | // Clean up the previous game | |
| 654 | + | pegpaint.Free; | |
| 655 | + | pegsol.Free; | |
| 656 | + | ||
| 657 | + | // Start with a new 7x7 game | |
| 658 | + | pegsol := TPegSolitaire.Create(7); | |
| 659 | + | pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas); | |
| 660 | + | ||
| 661 | + | // Initialize the cells to the classic game | |
| 662 | + | pegsol.InitializeBoard( ' ooo ' + LineEnding + | |
| 663 | + | ' ooo ' + LineEnding + | |
| 664 | + | 'ooooooo' + LineEnding + | |
| 665 | + | 'ooo.ooo' + LineEnding + | |
| 666 | + | 'ooooooo' + LineEnding + | |
| 667 | + | ' ooo ' + LineEnding + | |
| 668 | + | ' ooo ' ); | |
| 669 | + | end; | |
| 670 | + | ``` | |
| 671 | + | ||
| 672 | + | - Avaa PegDatastructures lähdekooditiedosto. | |
| 673 | + | - Lisää tämä aliohjelman esittely luokan public osioon: *InitializeBoard(const pBoard: ansistring);* | |
| 674 | + | ||
| 675 | + | ``` pascal | |
| 676 | + | ||
| 677 | + | public | |
| 678 | + | constructor Create(const pSize: TCellNums); | |
| 679 | + | procedure InitializeBoard(const pBoard: ansistring); | |
| 680 | + | ||
| 681 | + | property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell; | |
| 682 | + | property Size: TCellNums read FSize; | |
| 683 | + | ``` | |
| 684 | + | ||
| 685 | + | - Luo aliohjelman InitializeBoard:n runko. Paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>. | |
| 686 | + | ||
| 687 | + | 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. | |
| 688 | + | ||
| 689 | + | - Lisää *InitalizeBoard* koodi: | |
| 690 | + | ||
| 691 | + | ``` pascal | |
| 692 | + | procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring); | |
| 693 | + | var lst : TStringList; | |
| 694 | + | iRow,iCol: integer; | |
| 695 | + | s : string; | |
| 696 | + | begin | |
| 697 | + | // Create a list with the board text in it. This will split all lines | |
| 698 | + | // into individual lines, because of the LineEnding 'splitter'. | |
| 699 | + | lst := TStringList.Create; | |
| 700 | + | lst.Text := pBoard; | |
| 701 | + | ||
| 702 | + | // Process all lines one at a time | |
| 703 | + | for iRow := 0 to lst.Count-1 do | |
| 704 | + | if iRow < Size then // Make sure there is no overflow in the rows | |
| 705 | + | begin | |
| 706 | + | // Process a single line of text | |
| 707 | + | s := lst[iRow]; | |
| 708 | + | for iCol := 1 to length(s) do | |
| 709 | + | if iCol <= Size then // Make sure there is no overflow in the columns | |
| 710 | + | case s[iCol] of | |
| 711 | + | ' ': Cell[iRow+1,iCol] := ctNoAccess; | |
| 712 | + | '.': Cell[iRow+1,iCol] := ctEmpty; | |
| 713 | + | 'o': Cell[iRow+1,iCol] := ctPeg; | |
| 714 | + | end; | |
| 715 | + | end; | |
| 716 | + | ||
| 717 | + | // Clean up the list | |
| 718 | + | lst.Free; | |
| 719 | + | end; | |
| 720 | + | ``` | |
| 721 | + | ||
| 722 | + | 1. TStringList käytetään puskurina. Tämä toimii, koska *LineEnding*:a käytettiin rivien erottimena. | |
| 723 | + | 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*. | |
| 724 | + | ||
| 725 | + | 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: | |
| 726 | + | ||
| 727 | + | ``` pascal | |
| 728 | + | procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring); | |
| 729 | + | var iRow,iCol: integer; | |
| 730 | + | begin | |
| 731 | + | with TStringList.Create do | |
| 732 | + | begin | |
| 733 | + | Text := pBoard; | |
| 734 | + | for iRow := 0 to Min(Count-1, Size-1) do | |
| 735 | + | for iCol := 1 to Min(length(Strings[iRow]),Size) do | |
| 736 | + | case Strings[iRow][iCol] of | |
| 737 | + | ' ': Cell[iRow+1,iCol] := ctNoAccess; | |
| 738 | + | '.': Cell[iRow+1,iCol] := ctEmpty; | |
| 739 | + | 'o': Cell[iRow+1,iCol] := ctPeg; | |
| 740 | + | end; | |
| 741 | + | Free; | |
| 742 | + | end; | |
| 743 | + | end; | |
| 744 | + | ``` | |
| 745 | + | ||
| 746 | + | 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. | |
| 747 | + | ||
| 748 | + | - Suorita ohjelma. Kaikki soluissa on pelinappula kuten klassisessa Peg Solitaire pelissä. | |
| 749 | + | ||
| 750 | + | ## Tapahtumien uudelleenkäynti | |
| 751 | + | ||
| 752 | + | 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. | |
| 753 | + | ||
| 754 | + | - Tuo [[TMemo/fi|TMemo]]-komponentti lomakkeelle, TPaintBox pbPeg:n vasemmalla puolen (TMemo löytyy Standard välilehdeltä komponenttipaletilta). | |
| 755 | + | - Komponenttimuokkaimessa muuta Align arvoon *alClient*. Se vie kaiken jäljellä olevan tilan, joka on jäljellä TPaintbox. | |
| 756 | + | - Vaihda BorderSpacing.Around arvoon *4*. | |
| 757 | + | - Tyhjennä Lines ominaisuus (klikkaa kolme pistettä painiketta (...) ja poista teksti). Tuloksena pitäisi näyttää tältä: | |
| 758 | + | ||
| 759 | + | <figure> | |
| 760 | + | <img src="tutpeg_with_memo.png" title="tutpeg_with_memo.png" /> | |
| 761 | + | <figcaption>tutpeg_with_memo.png</figcaption> | |
| 762 | + | </figure> | |
| 763 | + | ||
| 764 | + | - Valitse TPaintBox pbPeg. | |
| 765 | + | - Komponenttimuokkaimessa valitse *Tapahtumat*-välilehti. | |
| 766 | + | ||
| 767 | + | On kaksi tapahtumaa, jotka ovat mielenkiintoisia: OnMouseDown ja OnMouseUp. Katsotaan mitä tapahtuu OnMouseDown tapahtumassa. | |
| 768 | + | ||
| 769 | + | - Komponenttimuokkaimessa valitse OnMouseDown tapahtuma. | |
| 770 | + | - Klikkaa kolme pisteettä painiketta (tämä luo rungon tapahtuman aliohjelmalle). | |
| 771 | + | ||
| 772 | + | 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. | |
| 773 | + | ||
| 774 | + | - Lisää seuraavat lauseet aliohjelmaan; se lisää muotoillun rivin TMemo-komponentin näyttämään paikkaa, missä hiirtä painettiin: | |
| 775 | + | ||
| 776 | + | ``` pascal | |
| 777 | + | Memo1.Append( format('Mouse down @ %dx%d',[X,Y]) ); | |
| 778 | + | ``` | |
| 779 | + | ||
| 780 | + | - Suorita ohjelma. (Paina <span class="kbd">F9</span>). | |
| 781 | + | - Klikkaa hiirellä pelilautaa useissa eri paikoissa. Muistion (TMemon) ruutu näyttää missä hiiri painettiin. | |
| 782 | + | - Lopeta ohjelma. | |
| 783 | + | ||
| 784 | + | 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. | |
| 785 | + | ||
| 786 | + | - Avaa PegDatastructures käännösyksikkö. | |
| 787 | + | - Lisää *TCellPosition* tietuetyyppi johon tallennetaan solun koordinaatit, tyyppien esittelyosioon: | |
| 788 | + | ||
| 789 | + | ``` pascal | |
| 790 | + | type | |
| 791 | + | TCellNums = 1..C_MAX; | |
| 792 | + | TCellType = (ctNoAccess, ctEmpty, ctPeg); | |
| 793 | + | TPegCells = array[TCellNums, TCellNums] of TCellType; | |
| 794 | + | ||
| 795 | + | TCellPosition = record | |
| 796 | + | Row: TCellNums; | |
| 797 | + | Col: TCellNums; | |
| 798 | + | end; | |
| 799 | + | ``` | |
| 800 | + | ||
| 801 | + | - Avaa PegSolPainter käännösyksikkö. | |
| 802 | + | - Lisää luokan julkiseen (public)osioon funktio CanvasXYtoCell (...): | |
| 803 | + | ||
| 804 | + | ``` pascal | |
| 805 | + | public | |
| 806 | + | constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas); | |
| 807 | + | ||
| 808 | + | procedure Repaint(const pCanvasWidth, pCanvasHeight: integer); | |
| 809 | + | function CanvasXYtoCell(const pX, pY: integer): TCellPosition; | |
| 810 | + | ``` | |
| 811 | + | ||
| 812 | + | - Luo funktion runko. Paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>. | |
| 813 | + | - Lisää seuraava koodi: | |
| 814 | + | ||
| 815 | + | ``` pascal | |
| 816 | + | function TPegSolPainter.CanvasXYtoCell(const pX, pY: integer): TCellPosition; | |
| 817 | + | begin | |
| 818 | + | result.Col := (pX div CellWidth) + 1; | |
| 819 | + | result.Row := (pY div CellHeight) + 1; | |
| 820 | + | end; | |
| 821 | + | ``` | |
| 822 | + | ||
| 823 | + | 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. | |
| 824 | + | ||
| 825 | + | - Lisää *CellWidth* ja *CellHeight* yksityisinä muuttujina *TPegSolPainter* luokkaan: | |
| 826 | + | ||
| 827 | + | ``` pascal | |
| 828 | + | TPegSolPainter = class | |
| 829 | + | private | |
| 830 | + | PegSol : TPegSolitaire; | |
| 831 | + | Canvas : TCanvas; | |
| 832 | + | CellWidth : integer; | |
| 833 | + | CellHeight : integer; | |
| 834 | + | ``` | |
| 835 | + | ||
| 836 | + | - Poista muuttujat *CellWidth* and *CellHeight* aliohjelman Repaint muuttujien esittelystä: | |
| 837 | + | ||
| 838 | + | ``` pascal | |
| 839 | + | procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer); | |
| 840 | + | var | |
| 841 | + | iRow, iCol : TCellNums; | |
| 842 | + | CellArea : TRect; | |
| 843 | + | begin | |
| 844 | + | ``` | |
| 845 | + | ||
| 846 | + | Nyt voidaan muuttaa aliohjelmaa, missä hiiren napin alaspainamisen koordinaattia käsiteltiin. Uuden funktion X / Y-paikka voidaan nyt sovittaa solun koordinaatteihin. | |
| 847 | + | ||
| 848 | + | - Avaa ufrmmain käännösyksikkö. | |
| 849 | + | - Paikallista *pbPegMouseDown* aliohjelma. | |
| 850 | + | - Muuta koodi: | |
| 851 | + | ||
| 852 | + | ``` pascal | |
| 853 | + | procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); | |
| 854 | + | var CellRC: TCellPosition; | |
| 855 | + | begin | |
| 856 | + | CellRC := pegpaint.CanvasXYtoCell(X,Y); | |
| 857 | + | Memo1.Append( format('Mouse down @ %dx%d',[CellRC.Row, CellRC.Col]) ); | |
| 858 | + | end; | |
| 859 | + | ``` | |
| 860 | + | ||
| 861 | + | Tehdään sama OnMouseUp tapahtumaan: | |
| 862 | + | ||
| 863 | + | - Luo aliohjelman runko OnMouseUp tapahtumaan. | |
| 864 | + | - Lisää tämä koodi: | |
| 865 | + | ||
| 866 | + | ``` pascal | |
| 867 | + | procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); | |
| 868 | + | begin | |
| 869 | + | with pegpaint.CanvasXYtoCell(X,Y) do | |
| 870 | + | Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) ); | |
| 871 | + | end; | |
| 872 | + | ``` | |
| 873 | + | ||
| 874 | + | 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 ...). | |
| 875 | + | ||
| 876 | + | Testataan tätä: | |
| 877 | + | ||
| 878 | + | - Suorita ohjelma. | |
| 879 | + | - Klikkaa ylhäällä vasemman puolista solua ja pidä hiiren nappi alhaalla. | |
| 880 | + | - Siirrä hiiren kohdistin toiseen soluun. | |
| 881 | + | - Vapauta hiiren painike. | |
| 882 | + | ||
| 883 | + | Joka kerta kun painaa hiiren painiketta ja vapauttaa sen, niin solu, jossa hiiren painiketta painettiin tai vapautettiin näkyy muistiossa. | |
| 884 | + | ||
| 885 | + | ## Liimataan asioita yhteen | |
| 886 | + | ||
| 887 | + | Tämä ohjelma toimii vähän kuin laskin ohjelma: | |
| 888 | + | ||
| 889 | + | 1. Käyttäjä valitsee pelinappulan jota siirretään painamalla hiiren painiketta. | |
| 890 | + | 2. Käyttäjä siirtää pelinappulan toiseen pelinappulan yli tyhjään soluun (hyppy). | |
| 891 | + | 3. Käyttäjä vapauttaa hiiren painikkeen ja siirto tapahtuu. | |
| 892 | + | ||
| 893 | + | 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. | |
| 894 | + | ||
| 895 | + | - Avaa ufrmmain käännösyksikkö. | |
| 896 | + | - Lisää muuttuja *FromCell* lomakkeen yksityiseksi muuttujaksi: | |
| 897 | + | ||
| 898 | + | ``` pascal | |
| 899 | + | ||
| 900 | + | private | |
| 901 | + | { private declarations } | |
| 902 | + | pegsol : TPegSolitaire; // The game data | |
| 903 | + | pegpaint: TPegSolPainter; // The paint class for the game | |
| 904 | + | FromCell: TCellPosition; // The peg that is going to leap | |
| 905 | + | ``` | |
| 906 | + | ||
| 907 | + | - Mene OnMouseDown tapahtumankäsittelijään. | |
| 908 | + | - Lisää seuraava rivi tähän aliohjelmaan: | |
| 909 | + | ||
| 910 | + | ``` pascal | |
| 911 | + | FromCell := pegpaint.CanvasXYtoCell(X,Y); | |
| 912 | + | ``` | |
| 913 | + | ||
| 914 | + | 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\>). | |
| 915 | + | ||
| 916 | + | - Paikallista OnMouseUp tapahtumakäsittelijä ja lisää seuraava rivi: | |
| 917 | + | ||
| 918 | + | ``` pascal | |
| 919 | + | pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y)); | |
| 920 | + | ``` | |
| 921 | + | ||
| 922 | + | *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. | |
| 923 | + | ||
| 924 | + | - Lisää seuraava rivi aliohjelmaan: | |
| 925 | + | ||
| 926 | + | ``` pascal | |
| 927 | + | pbPeg.Repaint; | |
| 928 | + | ``` | |
| 929 | + | ||
| 930 | + | Tämä pakottaa paintbox:n piirtämään itsensä, joten kaikki päivitetyt solut näyttävät oikean sisällön. | |
| 931 | + | ||
| 932 | + | Täydellinen OnMouseDown aliohjelma nyt näyttää tältä: | |
| 933 | + | ||
| 934 | + | ``` pascal | |
| 935 | + | procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); | |
| 936 | + | begin | |
| 937 | + | with pegpaint.CanvasXYtoCell(X,Y) do | |
| 938 | + | Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) ); | |
| 939 | + | pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y)); | |
| 940 | + | pbPeg.Repaint; | |
| 941 | + | end; | |
| 942 | + | ``` | |
| 943 | + | ||
| 944 | + | 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: | |
| 945 | + | ||
| 946 | + | ``` pascal | |
| 947 | + | state := pegsol.Cell[ FromCell.Row, FramCell.Col ] | |
| 948 | + | ``` | |
| 949 | + | ||
| 950 | + | Olisi mukavaa, jos voisi kirjoittaa jotain: | |
| 951 | + | ||
| 952 | + | ``` pascal | |
| 953 | + | state := pegsol.GetCell(FromCell) | |
| 954 | + | ``` | |
| 955 | + | ||
| 956 | + | Lisätään tämä peli luokkaan. | |
| 957 | + | ||
| 958 | + | - Avaa käännösyksikkö PegDatastructures. | |
| 959 | + | - Lisää yksityinen (private) funktio:*GetCell (const pPosition: TCellPosition): TCellType;* | |
| 960 | + | ||
| 961 | + | ``` pascal | |
| 962 | + | TPegSolitaire = class | |
| 963 | + | private | |
| 964 | + | FSize: TCellNums; | |
| 965 | + | PegCells: TPegCells; | |
| 966 | + | function GetCell(const pRow, pCol: TCellNums): TCellType; | |
| 967 | + | procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType); | |
| 968 | + | function GetCell(const pPosition: TCellPosition): TCellType; | |
| 969 | + | ``` | |
| 970 | + | ||
| 971 | + | Nyt on kaksi samannimistä GetCell funktiota, mutta niiden parametriluettelo on erilainen. Joten Lazarus tietää tarkalleen, milloin kutsua kyseistä funktiota. | |
| 972 | + | ||
| 973 | + | - Luo funktion runko (Paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>). | |
| 974 | + | - Lisätään seuraava koodi: | |
| 975 | + | ||
| 976 | + | ``` pascal | |
| 977 | + | result := Cell[pPosition.Row, pPosition.Col]; | |
| 978 | + | ``` | |
| 979 | + | ||
| 980 | + | 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. | |
| 981 | + | ||
| 982 | + | - Avaa käännösyksikkö PegDatastructures. | |
| 983 | + | - Lisää aliohjelma Leap luokan TPegSolitaire public osioon. | |
| 984 | + | ||
| 985 | + | ``` pascal | |
| 986 | + | public | |
| 987 | + | constructor Create(const pSize: TCellNums); | |
| 988 | + | procedure InitializeBoard(const pBoard: ansistring); | |
| 989 | + | procedure Leap(const pFromCell, pToCell: TCellPosition); | |
| 990 | + | ``` | |
| 991 | + | ||
| 992 | + | - Luo aliohjelman runko (Paina <span class="kbd">Ctrl</span>+<span class="kbd">Shift</span>+<span class="kbd">C</span>). | |
| 993 | + | ||
| 994 | + | 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. | |
| 995 | + | ||
| 996 | + | ``` pascal | |
| 997 | + | procedure TPegSolitaire.Leap(const pFromCell, pToCell: TCellPosition); | |
| 998 | + | var dx, dy: integer; | |
| 999 | + | JumpedCell: TCellPosition; | |
| 1000 | + | begin | |
| 1001 | + | // Verify that the start cell is occupied and the target cell is empty | |
| 1002 | + | // If not, leave the procedure via the EXIT. | |
| 1003 | + | if (GetCell(pFromCell) <> ctPeg) or | |
| 1004 | + | (GetCell(pToCell) <> ctEmpty) then | |
| 1005 | + | EXIT; | |
| 1006 | + | ||
| 1007 | + | // Calculate the horizontal and vertical distance between the cells | |
| 1008 | + | dx := abs(pFromCell.Col - pToCell.Col); | |
| 1009 | + | dy := abs(pFromCell.Row - pToCell.Row); | |
| 1010 | + | ||
| 1011 | + | // A valid move has one direction equal to zero and the other equal to 2 | |
| 1012 | + | if ((dx = 2) and (dy = 0)) | |
| 1013 | + | or ((dx = 0) and (dy = 2)) then | |
| 1014 | + | begin | |
| 1015 | + | // Determine the position of the jumped cell; it's in the middle | |
| 1016 | + | JumpedCell.Col := (pFromCell.Col + pToCell.Col) div 2; | |
| 1017 | + | JumpedCell.Row := (pFromCell.Row + pToCell.Row) div 2; | |
| 1018 | + | ||
| 1019 | + | // Final check: is there a peg in the jumped cell? | |
| 1020 | + | if GetCell(JumpedCell) = ctPeg then | |
| 1021 | + | begin | |
| 1022 | + | // Jump: clear the FromCell, empty the jumped cell and populate the ToCell | |
| 1023 | + | Cell[pFromCell.Row, pFromCell.Col] := ctEmpty; | |
| 1024 | + | Cell[JumpedCell.Row, JumpedCell.Col] := ctEmpty; | |
| 1025 | + | Cell[pToCell.Row, pToCell.Col] := ctPeg; | |
| 1026 | + | end; | |
| 1027 | + | end; | |
| 1028 | + | end; | |
| 1029 | + | ``` | |
| 1030 | + | ||
| 1031 | + | Usko tai älä, mutta nyt on toimiva Peg Solitaire lautapasianssi peliohjelma! | |
| 1032 | + | ||
| 1033 | + | - Suorita ohjelma. | |
| 1034 | + | - Paina hiiren painiketta yhden pelinappula solun päällä. | |
| 1035 | + | - Hyppää yli toisen pelinappula solun tyhjään soluun. | |
| 1036 | + | - Vapauta hiiren painike. | |
| 1037 | + | - Toista tätä... | |
| 1038 | + | ||
| 1039 | + | ## Puhdistusta | |
| 1040 | + | ||
| 1041 | + | Lomakkeelle meillä on vielä muistio komponentti, joka näyttää hiiren klikkauksella. Sitä enää todellakaan tarvita. | |
| 1042 | + | ||
| 1043 | + | - Poista muistio lomakkeelta. | |
| 1044 | + | - Poista muistion päivitykset MouseDown ja MouseUp tapahtumat. | |
| 1045 | + | ||
| 1046 | + | ``` pascal | |
| 1047 | + | procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); | |
| 1048 | + | begin | |
| 1049 | + | FromCell := pegpaint.CanvasXYtoCell(X,Y); | |
| 1050 | + | end; | |
| 1051 | + | ||
| 1052 | + | procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); | |
| 1053 | + | begin | |
| 1054 | + | pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y) ); | |
| 1055 | + | pbPeg.Repaint; | |
| 1056 | + | end; | |
| 1057 | + | ``` | |
| 1058 | + | ||
| 1059 | + | - Siirry lomakkeelle (<span class="kbd">F12</span>). | |
| 1060 | + | - Valitse TPaintBox pbPeg | |
| 1061 | + | - Komponenttimuokkaimessa vaihda ominaisuus Align arvoon *alClient* (Ominaisuudet välilehti). | |
| 1062 | + | - 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). | |
| 1063 | + | ||
| 1064 | + | ### Mitä seuraavaksi? | |
| 1065 | + | ||
| 1066 | + | 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. | |
| 1067 | + | ||
| 1068 | + | ## Luodaan enemmän pelilautoja | |
| 1069 | + | ||
| 1070 | + | 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. | |
| 1071 | + | ||
| 1072 | + | - Valitse valikosta Tiedosto / Uusi ...; Tämä avaa "Uusi..."- ikkunan. | |
| 1073 | + | - Valitse vaihtoehto Moduuli / Teksti ja valitse OK. | |
| 1074 | + | - Kirjoita seuraava teksti (sen pitäisi näyttää tutulta :-)): | |
| 1075 | + | ||
| 1076 | + | ``` pascal | |
| 1077 | + | ooo | |
| 1078 | + | ooo | |
| 1079 | + | ooooooo | |
| 1080 | + | ooo.ooo | |
| 1081 | + | ooooooo | |
| 1082 | + | ooo | |
| 1083 | + | ooo | |
| 1084 | + | ``` | |
| 1085 | + | ||
| 1086 | + | - Valitse valikosta Tiedosto / Tallenna. | |
| 1087 | + | - Tallenna tiedosto projektin bin kansioon nimellä *classic.txt*. | |
| 1088 | + | ||
| 1089 | + | Lisää toinen tiedosto: | |
| 1090 | + | ||
| 1091 | + | - Valitse valikosta Tiedosto / Uusi .... | |
| 1092 | + | - Valitse vaihtoehto Moduuli / Teksti ja valitse OK. | |
| 1093 | + | - Kirjoita seuraava teksti: | |
| 1094 | + | ||
| 1095 | + | ``` pascal | |
| 1096 | + | ... | |
| 1097 | + | .o. | |
| 1098 | + | ..ooo.. | |
| 1099 | + | .ooooo. | |
| 1100 | + | ooooooo | |
| 1101 | + | ... | |
| 1102 | + | ... | |
| 1103 | + | ``` | |
| 1104 | + | ||
| 1105 | + | - Valitse valikosta Tiedosto / Tallenna. | |
| 1106 | + | - Tallenna tiedosto bin kansioon nimellä *triangle.txt*. | |
| 1107 | + | ||
| 1108 | + | On monia tapoja luoda ja avata uusia pelilautoja. Tässä opetusohjelmassa käytetään valikko lähestymistapaa (perusteellisempi kuvaus valikosta on jossain toisessa opetusohjelmassa). | |
| 1109 | + | ||
| 1110 | + | - Avaa ufrmmain käännösyksikkö. | |
| 1111 | + | - Valitse TMainMenu-komponentti komponenttipaletin Standard-välilehdeltä. | |
| 1112 | + | - Klikkaa lomakkeella (sillä ei ole väliä, jos osut paintbox:n). Kuvake näkyy osoittaa, että valikko on nyt saatavilla olevat. | |
| 1113 | + | - Tuplaklikkaa MainMenu1 kuvaketta. Tämä avaa valikkomuokkaimen. Yksi kohde on jo käytettävissä, nimeltään *New Item1*. | |
| 1114 | + | - Komponenttimuokkaimella muutaa sen Caption tekstiksi *&File* (sisältyy et-merkillä, sitä käytetään vetämään alaviivat kun painat Alt päävalikkoon). | |
| 1115 | + | - Klikkaa hiiren oikealla File kohdasta valikkomuokkaimessa ja valitse *Luo alivalikko* ponnahdusikkunasta. | |
| 1116 | + | - Muuta Caption tekstiksi *E&xit* (tämä on tuttu sovelluksen lopeta kohta). | |
| 1117 | + | - Klikkaa hiiren oikealla File kohtaa. | |
| 1118 | + | - Valitse *Lisää uusi kohta (jälkeen)* ponnahdusikkunasta. | |
| 1119 | + | - Muuta Caption tekstiksi kyseisen kohtaan *&Game*. | |
| 1120 | + | - Klikkaa hiiren oikealla Game kohdasta valikkomuokkaimessa ja valitse *Luo alivalikko* ponnahdusikkunasta. | |
| 1121 | + | - Muuta Caption tekstiksi *Load from file...*. | |
| 1122 | + | ||
| 1123 | + | Valikko nyt näyttää tältä: | |
| 1124 | + | ||
| 1125 | + | <figure> | |
| 1126 | + | <img src="tutpeg_mainmenu.png" title="tutpeg_mainmenu.png" /> | |
| 1127 | + | <figcaption>tutpeg_mainmenu.png</figcaption> | |
| 1128 | + | </figure> | |
| 1129 | + | ||
| 1130 | + | Valikko on myös näkyvissä lomakkeen yläosassa. | |
| 1131 | + | ||
| 1132 | + | - Klikkaa *File*-valikosta kohtaa; Tämä avaa alivalikon ja *Exit* on näkyvissä. | |
| 1133 | + | - 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. | |
| 1134 | + | - Lisää seuraava rivi luotuun koodin runkoon: | |
| 1135 | + | ||
| 1136 | + | ``` pascal | |
| 1137 | + | Close; | |
| 1138 | + | ``` | |
| 1139 | + | ||
| 1140 | + | Tämä sulkee lomakkeen. Ja koska se on ainoa lomake niin myös sovellus loppuu. | |
| 1141 | + | ||
| 1142 | + | 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. | |
| 1143 | + | ||
| 1144 | + | - Mene lomakkeelle. | |
| 1145 | + | - Komponentti paletulta valitse *Dialogs* välilehti. | |
| 1146 | + | - Valitse TOpenDialog komponentti (klikkaa sitä). | |
| 1147 | + | - 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ä: | |
| 1148 | + | ||
| 1149 | + | <figure> | |
| 1150 | + | <img src="tutpeg_mainform.png" title="tutpeg_mainform.png" /> | |
| 1151 | + | <figcaption>tutpeg_mainform.png</figcaption> | |
| 1152 | + | </figure> | |
| 1153 | + | ||
| 1154 | + | - 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. | |
| 1155 | + | - Nyt halutaan vain tekstitiedostoja joten lisätään tälläinen suodatinrivi: *Peg Solitaire TextFiles (* .txt) \| * .txt* (katso kuva alla). | |
| 1156 | + | - Paina OK. | |
| 1157 | + | ||
| 1158 | + | <figure> | |
| 1159 | + | <img src="tutpeg_filterdialog.png" title="tutpeg_filterdialog.png" /> | |
| 1160 | + | <figcaption>tutpeg_filterdialog.png</figcaption> | |
| 1161 | + | </figure> | |
| 1162 | + | ||
| 1163 | + | - Komponenttimuokkaimessa muuta Options.ofFileMustExist totta. Tämä pakottaa dialogi sallii vain nykyiset tiedostot voidaan valita. | |
| 1164 | + | ||
| 1165 | + | Hyvä niin. Nyt todella avata tiedoston, kun valikko on valittuna: | |
| 1166 | + | ||
| 1167 | + | - Lomakkeella klikkaa Game valikosta. Tämä avaa pudotusvalikon. | |
| 1168 | + | - Klikkaa *Load from file...*. Odotetusti tämä luo .... tapahtumankäsittelijänä. | |
| 1169 | + | - Lisää seuraava koodi tapahtumakäsittelijään: | |
| 1170 | + | ||
| 1171 | + | ``` pascal | |
| 1172 | + | if OpenDialog1.Execute then | |
| 1173 | + | ShowMessage(OpenDialog1.FileName); | |
| 1174 | + | ``` | |
| 1175 | + | ||
| 1176 | + | 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. | |
| 1177 | + | ||
| 1178 | + | - Suorita sovellus. | |
| 1179 | + | - Valitse valikosta *Game/Load from file...*. | |
| 1180 | + | - Valitse *triangle.txt* ja paina Avaa. | |
| 1181 | + | - Ikkuna ponnahtaa joka osoittaa meille valitun tiedostonimen. | |
| 1182 | + | - Lopeta ohjelma. | |
| 1183 | + | ||
| 1184 | + | Nyt viimeistellään latauskoodi. Koodi on melko itsestään selvä ja käyttää uudelleen joitakin aliohjelmia jotka on jo luotu. | |
| 1185 | + | ||
| 1186 | + | - Mene lomakkeella ja klikkaa Game valikosta. Tämä avaa pudotusvalikon. | |
| 1187 | + | - Klikkaa *Load from file...*. Odotetusti tämä avaa tapahtumankäsittelijän. | |
| 1188 | + | - Korvaa koodin tällä: | |
| 1189 | + | ||
| 1190 | + | ``` pascal | |
| 1191 | + | procedure TfrmMain.MenuItem4Click(Sender: TObject); | |
| 1192 | + | begin | |
| 1193 | + | // Open the pop up dialog | |
| 1194 | + | if OpenDialog1.Execute then | |
| 1195 | + | begin | |
| 1196 | + | // Start with a new empty board | |
| 1197 | + | StartNewGame; | |
| 1198 | + | ||
| 1199 | + | // Dynamically create a stringlist to load the board layout | |
| 1200 | + | with TStringList.Create do | |
| 1201 | + | begin | |
| 1202 | + | // Load the board layout from the textfile | |
| 1203 | + | LoadFromFile(OpenDialog1.FileName); | |
| 1204 | + | ||
| 1205 | + | // Initialize the board with the file's contents | |
| 1206 | + | pegsol.InitializeBoard(Text); | |
| 1207 | + | ||
| 1208 | + | // Clean up the stringlist | |
| 1209 | + | Free | |
| 1210 | + | end; | |
| 1211 | + | ||
| 1212 | + | // After loading the new board update the paintbox | |
| 1213 | + | pbPeg.Repaint; | |
| 1214 | + | end; | |
| 1215 | + | end; | |
| 1216 | + | ``` | |
| 1217 | + | ||
| 1218 | + | Ja se kaikki on siinä! | |
| 1219 | + | ||
| 1220 | + | Suorita ohjelma, avaa pelilaudan pohjatiedosto ja pelaa peliä! | |
| 1221 | + | ||
| 1222 | + | ## Silmänruokaa | |
| 1223 | + | ||
| 1224 | + | 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: | |
| 1225 | + | ||
| 1226 | + | - Avaa PegSolPainter käännösyksikkö. | |
| 1227 | + | - Lisää kolme (3) kuvamuuttujaa private osioon: | |
| 1228 | + | ||
| 1229 | + | ``` pascal | |
| 1230 | + | TPegSolPainter = class | |
| 1231 | + | private | |
| 1232 | + | PegSol : TPegSolitaire; | |
| 1233 | + | Canvas : TCanvas; | |
| 1234 | + | CellWidth : integer; | |
| 1235 | + | CellHeight : integer; | |
| 1236 | + | ||
| 1237 | + | ImgNoAccess: TPicture; | |
| 1238 | + | ImgEmpty : TPicture; | |
| 1239 | + | ImgPeg : TPicture; | |
| 1240 | + | ``` | |
| 1241 | + | ||
| 1242 | + | - Lisää kuvan latausaliohjelma public osioon. | |
| 1243 | + | ||
| 1244 | + | ``` pascal | |
| 1245 | + | public | |
| 1246 | + | constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas); | |
| 1247 | + | procedure Repaint(const pCanvasWidth, pCanvasHeight: integer); | |
| 1248 | + | function CanvasXYtoCell(const pX, pY: integer): TCellPosition; | |
| 1249 | + | procedure LoadImage(const pCellType: TCellType; const pFilename: string); | |
| 1250 | + | ``` | |
| 1251 | + | ||
| 1252 | + | - Luo aliohjelman runko. | |
| 1253 | + | - Lisää koodi: | |
| 1254 | + | ||
| 1255 | + | ``` pascal | |
| 1256 | + | procedure TPegSolPainter.LoadImage(const pCellType: TCellType; const pFilename: string); | |
| 1257 | + | ||
| 1258 | + | procedure UpdateImage(var pImg: TPicture; const pNewImage: TPicture); | |
| 1259 | + | begin | |
| 1260 | + | pImg.Free; | |
| 1261 | + | pImg := pNewImage | |
| 1262 | + | end; | |
| 1263 | + | ||
| 1264 | + | var pic: TPicture; | |
| 1265 | + | begin | |
| 1266 | + | // Make sure the file exists | |
| 1267 | + | if FileExists(pFilename) then | |
| 1268 | + | begin | |
| 1269 | + | // First load the picture in a local variable | |
| 1270 | + | pic := TPicture.Create; | |
| 1271 | + | pic.LoadFromFile(pFilename); | |
| 1272 | + | ||
| 1273 | + | // Now update the required image based on the pCelltype parm | |
| 1274 | + | case pCelltype of | |
| 1275 | + | ctNoAccess: UpdateImage(ImgNoAccess, pic); | |
| 1276 | + | ctEmpty : UpdateImage(ImgEmpty, pic); | |
| 1277 | + | ctPeg : UpdateImage(ImgPeg, pic); | |
| 1278 | + | end; | |
| 1279 | + | end; | |
| 1280 | + | end; | |
| 1281 | + | ``` | |
| 1282 | + | ||
| 1283 | + | 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. | |
| 1284 | + | ||
| 1285 | + | 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. | |
| 1286 | + | ||
| 1287 | + | - Paikallista Repaint aliohjelma. | |
| 1288 | + | - Case lauseessa, muuta ctNoAccess, ctEmpty ja ctPeg osiot seuraavanlaiseksi: | |
| 1289 | + | ||
| 1290 | + | ``` pascal | |
| 1291 | + | ||
| 1292 | + | // And now draw the cell based on the cell's contents | |
| 1293 | + | case pegsol.Cell[iRow,iCol] of | |
| 1294 | + | ||
| 1295 | + | ctNoAccess: // Draw cells that are not accessible | |
| 1296 | + | if not assigned(ImgNoAccess) then | |
| 1297 | + | begin | |
| 1298 | + | Canvas.Brush.Color := clGray; | |
| 1299 | + | Canvas.Rectangle(CellArea); | |
| 1300 | + | end | |
| 1301 | + | else with ImgNoAccess do | |
| 1302 | + | Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height)); | |
| 1303 | + | ||
| 1304 | + | ctEmpty: // Draw cells that are currently empty | |
| 1305 | + | if not assigned(ImgEmpty) then | |
| 1306 | + | begin | |
| 1307 | + | Canvas.Brush.Color := clBlue; | |
| 1308 | + | Canvas.Rectangle(CellArea); | |
| 1309 | + | end | |
| 1310 | + | else with ImgEmpty do | |
| 1311 | + | Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height)); | |
| 1312 | + | ||
| 1313 | + | ctPeg: // Draw cells that are occupied | |
| 1314 | + | if not assigned(ImgPeg) then | |
| 1315 | + | begin | |
| 1316 | + | Canvas.Brush.Color := clBlue; | |
| 1317 | + | Canvas.Rectangle(CellArea); // Erase the background first | |
| 1318 | + | Canvas.Brush.Color := clGreen; | |
| 1319 | + | Canvas.Ellipse(CellArea); | |
| 1320 | + | end | |
| 1321 | + | else with ImgPeg do | |
| 1322 | + | Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height)); | |
| 1323 | + | ||
| 1324 | + | end; | |
| 1325 | + | ``` | |
| 1326 | + | ||
| 1327 | + | Lisätään kuva latauskoodi piirtoluokkaan. | |
| 1328 | + | ||
| 1329 | + | - Avaa ufrmmain -käännösyksikkö. | |
| 1330 | + | - Paikallista aliohjelma StartNewGame. | |
| 1331 | + | - Lisää LoadImage kutsu *pegpaint* olion luomisen jälkeen: | |
| 1332 | + | ||
| 1333 | + | ``` pascal | |
| 1334 | + | // Start with a new 7x7 game | |
| 1335 | + | pegsol := TPegSolitaire.Create(7); | |
| 1336 | + | pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas); | |
| 1337 | + | ||
| 1338 | + | pegpaint.LoadImage(ctNoAccess, 'tutpeg_cellforbidden.jpg'); | |
| 1339 | + | pegpaint.LoadImage(ctEmpty, 'tutpeg_cellempty.jpg'); | |
| 1340 | + | pegpaint.LoadImage(ctPeg, 'tutpeg_celloccupied.jpg'); | |
| 1341 | + | ``` | |
| 1342 | + | ||
| 1343 | + | Varmista, että kuvat ladataan ja laitetaan ohjelman *bin* kansioon, tai vielä parempaa, luot omat kuvat. Huom. kun lataat allaolevia kuvia, klikkaa niitä ensin. | |
| 1344 | + | ||
| 1345 | + |    | |
| 1346 | + | ||
| 1347 | + | - Nyt kun testataan Peg Solitaire peliä. Toivottavasti se näyttää suunnilleen samanlaiselta kuin kuva tämän opetusohjelman alussa ... | |
| 1348 | + | ||
| 1349 | + | ## Viimeinen "helmi" | |
| 1350 | + | ||
| 1351 | + | Useimmilla ohjelmilla on oma kuvake, joka näkyvät valikon ja tiedoston selaus ikkunoissa. On helppo lisätä oma kuvake sovellukseen. | |
| 1352 | + | ||
| 1353 | + | - Valitse valikosta Projekti / Projektikohtaiset asetukset .... | |
| 1354 | + | - Klikkaa Projektin asetukset/ Sovellus. Tämä näyttää ikkunassa sovelluksen kuvaketta. | |
| 1355 | + | - Klikkaa Lataa kuvake ja lataa siihen sopiva kuvake tai ladata marmorikuvan joka on tässä alla: | |
| 1356 | + | ||
| 1357 | + | <figure> | |
| 1358 | + | <img src="tutpeg_marble.png" title="tutpeg_marble.png" /> | |
| 1359 | + | <figcaption>tutpeg_marble.png</figcaption> | |
| 1360 | + | </figure> | |
| 1361 | + | ||
| 1362 | + | - Paina OK. | |
| 1363 | + | ||
| 1364 | + | Sovelluksella on nyt oma kuvake, joka näkyy valikossa ja tiedoston selaus ikkunassa. | |
| 1365 | + | ||
| 1366 | + | --- | |
| 1367 | + | *From the Free Pascal wiki, page "Peg Solitaire tutorial/fi", as it stood on 2019-07-13 after a change by Djzepi, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
