forked from SquareBracketAssociates/UpdatedPharoByExample
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFirstApplication.pier
More file actions
781 lines (601 loc) · 37.4 KB
/
FirstApplication.pier
File metadata and controls
781 lines (601 loc) · 37.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
!A first application
@cha:firstApp
In this chapter, we will develop a simple game: *Lights
Out>http://en.wikipedia.org/wiki/Lights_Out_(game)*. Along the way we will
demonstrate most of the tools that Pharo programmers use to construct and debug
their programs, and show how programs are exchanged with other developers. We
will see the browser, the object inspector, the debugger and the Monticello
package browser.
+The Lights Out game board>file://figures/gameLO.png|width=30|label=fig:gameLO+
!!The Lights Out game
To show you how to use Pharo's programming tools, we will build a simple game
called Lights Out. The game board consists of rectangular array of light yellow
cells. When you click on one of the cells, the four surrounding
cells turn blue. Click again, and they toggle back to light yellow. The object
of the game is to turn blue as many cells as possible.
The Lights Out game is made up of two kinds of objects: the game board itself,
and 100 individual cell objects. The Pharo code to implement the game will
contain two classes: one for the game and one for the cells. We will now show
you how to define these classes using the Pharo programming tools.
!!Creating a new Package
@fstApp:Packages
We have already seen the browser in Chapter
*: A Quick Tour of Pharo>../PharoTour/PharoTour.pillar@cha:tour* where we
learned how to navigate to classes and methods, and saw how to define new
methods. Now we will see how to create packages and classes.
From the ==World== menu, open a System Browser. Right-click on an existing
package in the Package pane and select ==Add package...== from the menu
(see Figure *@fig:createPackage*). Type the name of the new package (we
will use ==PBE-LightsOut==) in the dialog box and click ==OK== (or just
press the return key). The new package is created, and positioned
alphabetically in the list of packages.
!!Defining the class ==LOCell==
At this point there are of course no classes in the new package. However, the
main editing pane displays a template to make it easy to create a new class (see
Figure *@fig:createPackage*).
This template shows us a Smalltalk expression that sends a message to a class
called ==Object==, asking it to create a subclass called ==NameOfSubClass==. The
new class has no variables, and should belong to the category (package)
==PBE-LightsOut==.
+Create a Package and class template>file://figures/createPackageFull.png|width=100|label=fig:createPackage+
!!!On categories vs. packages
Historically, Pharo packages were implemented as "categories". With the newer
versions of Pharo, the term ""category"" is being deprecated, and replaced
exclusively by ""package"". However, the method for creating subclasses
(explained in the next section) still refers to the class's package as
==category:==. Aside from subclass creation, to maintain consistency, we will
only use the term ""package"" in this book, to denote a collection or related
classes (and extension methods to other, existing classes). The Pharo package is
also what you will be using to version your source code using the Monticello
versioning tool.
!!!Creating a new class
We simply modify the template to create the class that we really want.
Modify the class creation template as follows:
- Replace ==Object== with ==SimpleSwitchMorph==.
- Replace ==NameOfSubClass== with ==LOCell==.
- Add ==mouseAction== to the list of instance variables.
The result should look like this:
[[[language=smalltalk|caption=Defining the classLOCell|label=scr:ClassLOCell
SimpleSwitchMorph subclass: #LOCell
instanceVariableNames: 'mouseAction'
classVariableNames: ''
category: 'PBE-LightsOut'
]]]
This new definition consists of a Smalltalk expression that sends a message to
the existing class ==SimpleSwitchMorph==, asking it to create a subclass called
==LOCell==. (Actually, since ==LOCell== does not exist yet, we passed the symbol
==\#LOCell== as an argument, representing the name of the class to create.) We
also tell it that instances of the new class should have a ==mouseAction==
instance variable, which we will use to define what action the cell should take
if the mouse should click on it.
+The class-creation Template>file://figures/createClass.png|width=75|label=fig:createClass+
At this point you still have not created anything. Note that the top right of
the panel changed to orange (see Figure *@fig:createClass*). This means that
there are unsaved changes. To actually send this subclass message, you must save
(accept) the source code.
Either right-click and select ==Accept==, or use the shortcut ==CMD-s== (for
"Save"). The message will be sent to ==SimpleSwitchMorph==, which will cause the
new class to be compiled.
+The newly-created class LOCell>file://figures/pannelAtNewClass.png|width=100|label=fig:pannelAtNewClass+
Once the class definition is accepted, the class will be created and appear in
the class pane of the browser (see Figure *@fig:pannelAtNewClass*). The editing
pane now shows the class definition. The Comments pane on the bottom right will
remind you to write a few words describing the purpose of the class (if you
don't see that pane, click on the ==Comments== button to toggle it open). This
is called a ''class comment'', and it is quite important to write one that will
give other programmers a high-level overview of the purpose of this class.
Smalltalkers put a very high value on the readability of their code, and
detailed comments in methods are unusual: the philosophy is that the code should
speak for itself. (If it doesn't, you should refactor it until it does!) A class
comment need not contain a detailed description of the class, but a few words
describing its overall purpose are vital if programmers who come after you are
to know whether to spend time looking at this class.
!!Adding methods to a class
Now let's add some methods to our class.
Select the protocol ==no messages== in the protocol pane. You will see a
template for method creation in the editing pane. Select the template text, and
replace it by the following:
[[[language=smalltalk|lineNumber=true|caption=Initializing instance of LOCell|label=scr:InitLOCell
initialize
super initialize.
self label: ''.
self borderWidth: 2.
bounds := 0 @ 0 corner: 16 @ 16.
offColor := Color paleYellow.
onColor := Color paleBlue darker.
self useSquareCorners.
self turnOff
]]]
Note that the characters =='\'== on line 3 are two separate single quotes with
nothing between them, not a double quote! ==\'\'== denotes the empty string.
Another way to create an empty string is ==String new.==
""Accept"" this method definition.
Notice that the method is called ==initialize==. The name is very significant!
By convention, if a class defines a method named ==initialize==, it will be
called right after the object is created. So, when we evaluate ==LOCell new==,
the message initialize will be sent automatically to this newly created object.
Initialize methods are used to set up the state of objects, typically to set
their instance variables; this is exactly what we are doing here.
+The newly-created method initialize>file://figures/createMethod.png|width=100|label=fig:createMethod+
The first thing that this method does (line 2) is to execute the ==initialize==
method of its superclass, ==SimpleSwitchMorph==. The idea here is that any
inherited state will be properly initialized by the initialize method of the
superclass. It is always a good idea to initialize inherited state by sending
==super initialize== before doing anything else. We don't know exactly what
==SimpleSwitchMorph==’s ==initialize== method will do (and we don't care), but
it's a fair bet that it will set up some instance variables to hold reasonable
default values. So we had better call it, or we risk starting in an unclean
state.
The rest of the method sets up the state of this object. Sending ==self label:
\'\'==, for example, sets the label of this object to the empty string.
The expression ==0\@0 corner: 16\@16== probably needs some explanation. ==0\@0==
represents a ==Point== object with ==x== and ==y== coordinates both set to 0. In
fact, ==0\@0== sends the message ==@== to the number 0 with argument 0. The
effect will be that the number 0 will ask the ==Point== class to create a new
instance with coordinates ==(0,0)==. Now we send this newly created point the
message ==corner: 16\@16==, which causes it to create a ==Rectangle== with
corners ==0\@0== and ==16\@16==. This newly created rectangle will be assigned
to the ==bounds== variable, inherited from the superclass.
Note that the origin of the Pharo screen is the top left, and the ==y==
coordinate increases downwards.
The rest of the method should be self-explanatory. Part of the art of writing
good Smalltalk code is to pick good method names so that the code can be
read like a kind of pidgin English. You should be able to imagine the object
talking to itself and saying \"Self, use square corners!\", \"Self, turn off\!\".
+The method initialize into the pane>file://figures/initialize.png|width=50|label=fig:initialize+
Notice that there is a little green arrow next to your method (see
Figure *@fig:initialize*). This means the method exists in the super class and is
overridden in your class.
!!Inspecting an object
You can test the effect of the code you have written by creating a new
==LOCell== object and inspecting it.
Open a ==Playground==. Type the expression ==LOCell new== and ""Inspect"" it.
+The inspector used to examine a LOCell object>file://figures/inspectLOCell.png|width=100|label=fig:inspectLOCell+
The left-hand column of the inspector shows a list of instance variables and the
value of the instance variable is shown in the right column (see Figure
*@fig:inspectLOCell*).
+The inspector when we click on an instance variable>file://figures/inspectBounds.png|width=100|label=fig:inspectBounds+
If you click on an instance variable the inspector will open a new pane with the
detail of the instance variable (see Figure *@fig:inspectBounds*).
The bottom pane of the inspector is a mini-playground. It's useful because in
this playground the pseudo-variable ==self== is bound to the object selected.
Go to that Playground at the bottom of the pane and type the text ==self bounds:
(200\@200 corner: 250\@250)== ""do it"". Click on the ==update== button at the
top right of the pane (see Figure *@fig:updateInspector*). The ==bounds==
variable should change in the inspector. Now type the text ==self openInWorld==
in the mini-playground and ""Do it"".
+Update inspector button>file://figures/updateInspector.png|width=20|label=fig:updateInspector+
The cell should appear near the top left-hand corner of the screen, indeed,
exactly where its bounds say that it should appear. Meta-click on the
cell to bring up the Morphic halo. Move the cell with the brown (next to
top-right) handle and resize it with the yellow (bottom-right) handle. Notice
how the bounds reported by the inspector also change. (You may have to
click refresh to see the new bounds value.)
+An LOCell open in world>file://figures/LOCellOpenInWorld.png|width=100|label=fig:LOCellOpenInWorld+
Delete the cell by clicking on the ==x== in the pink handle.
!!Defining the class ==LOGame==
Now let's create the other class that we need for the game, which we will name
==LOGame==.
Make the class definition template visible in the browser main window. Do this
by clicking on the package name (or right-clicking on the Class pane and
selecting ==Add Class==). Edit the code so that it reads as follows, and
""Accept"" it.
[[[language=smalltalk|caption=Defining the LOGame class|label=scr:DefineLOGame
BorderedMorph subclass: #LOGame
instanceVariableNames: ''
classVariableNames: ''
category: 'PBE-LightsOut'
]]]
Here we subclass ==BorderedMorph==. ==Morph== is the superclass of all of the
graphical shapes in Pharo, and (unsurprisingly) a ==BorderedMorph== is a
==Morph== with a border. We could also insert the names of the instance
variables between the quotes on the second line, but for now, let's just leave
that list empty.
Now let's define an ==initialize== method for ==LOGame==. Type the following
into the browser as a method for ==LOGame== and try to ""Accept"" it.
[[[language=smalltalk|lineNumber=true|caption=Initialize the game|label=scr:InitiGame
initialize
| sampleCell width height n |
super initialize.
n := self cellsPerSide.
sampleCell := LOCell new.
width := sampleCell width.
height := sampleCell height.
self bounds: (5 @ 5 extent: (width * n) @ (height * n) + (2 * self borderWidth)).
cells := Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ]
]]]
Pharo will complain that it doesn't know the meaning of ==cells== (see Figure
*@fig:doesntKnowCell*). It will offer you a number of ways to fix this.
+Declaring a new instance variable>file://figures/doesntKnowCell.png|width=75|label=fig:doesntKnowCell+
Choose ""Declare new instance variable"", because we want ==cells== to be an
instance variable.
At this stage if you open a ==Playground== and type ==LOGame new== and ""Do it""
Pharo will complain that it doesn't know the meaning of some of the terms (see
Figure *@fig:doesntKnowCellsPerSide*). It will tell you that it doesn't know of
a message ==cellsPerSide==, and will open a debugger.
+Pharo detecting an unknown selector>file://figures/doesntKnowCellsPerSide.png|width=100|label=fig:doesntKnowCellsPerSide+
But ==cellsPerSide== is not a mistake; it is just a method that we haven't yet
defined. We will do so, shortly.
+Define CellsPerSide methods>file://figures/defineCellsPerSide.png|width=100|label=fig:defineCellsPerSide+
So, click on ==Create==, select ""LOGame"", the class which will contains the
method, click on ""ok"" (see Figure *@fig:defineCellsPerSide*). Here you can
write your method. But for now we'll leave this method like this. We'll return
to it later.
If you now look at the class definition once again (which you can do by clicking
on ""LOGame"" on the second pane), you will see that the
browser has modified it to include the instance variable ==cells==.
Let's look at this ==initialize== method. At line 2, ==\| sampleCell width
height n \|==, declares 4 temporary variables. They are called temporary
variables because their scope and lifetime are limited to this method. Temporary
variables with explanatory names are helpful in making code more readable. Lines
4\-7 set the value of these variables.
How big should our game board be? Big enough to hold some integral number of
cells, and big enough to draw a border around them. How many cells is the right
number? 5? 10? 100? We don't know yet, and if we did, we would probably change
our minds later. So we delegate the responsibility for knowing that number to
another method, which we will name ==cellsPerSide==, and which we will write in
a minute or two. Don't be put off by this: it is actually good practice to code
by referring to other methods that we haven't yet defined. Why? Well, it wasn't
until we started writing the ==initialize== method that we realized that we
needed it. And at that point, we can give it a meaningful name, and move on,
without interrupting our flow.
The fourth line uses this method, ==n := self cellsPerSide.== sends the message
==cellsPerSide== to ==self==, i.e., to this very object. The response, which
will be the number of cells per side of the game board, is assigned to ==n==.
The next three lines create a new ==LOCell== object, and assign its width and
height to the appropriate temporary variables.
Line 8 sets the bounds of the new object. Without worrying too much about the
details just yet, believe us that the expression in parentheses creates a
square with its origin (i.e., its top-left corner) at the point (5,5) and its
bottom-right corner far enough away to allow space for the right number of
cells.
The last line sets the ==LOGame== object's instance variable ==cells== to a
newly created ==Matrix== with the right number of rows and columns. We do this
by sending the message ==new: tabulate:== to the ==Matrix== class (classes are
objects too, so we can send them messages). We know that ==new: tabulate:==
takes two arguments because it has two colons (:) in its name. The arguments go
right after the colons. If you are used to languages that put all of the
arguments together inside parentheses, this may seem weird at first. Don't
panic, it's only syntax! It turns out to be a very good syntax because the name
of the method can be used to explain the roles of the arguments. For example, it
is pretty clear that ==Matrix rows: 5 columns: 2== has 5 rows and 2 columns, and
not 2 rows and 5 columns.
==Matrix new: n tabulate: [ :i :j \| self newCellAt: i at: j ]== creates a new n
X n matrix and initializes its elements. The initial value of each element will
depend on its coordinates. The (i,j)^^th^^ element will be initialized to the
result of evaluating ==self newCellAt: i at: j.==
!!Organizing methods into protocols
Before we define any more methods, let's take a quick look at the third pane at
the top of the browser. In the same way that the first pane of the browser lets
us categorize classes into packages so we are not overwhelmed by a very long
list of class names in the class pane, so the protocol pane lets us categorize
methods so that we are not overwhelmed by a very long list of method names in
the method pane. These groups of methods are called "protocols".
If there are only a few methods in a class, the extra level of hierarchy
provided by protocols is not really necessary. This is why the browser also
offers us the ==\-\-all\-\-== virtual protocol, which, you will not be surprised
to learn, contains all of the methods in the class.
+Automatically categorize all uncategorized methods>file://figures/categorizeProtocols.png|width=100|label=fig:categorizeProtocoles+
If you have followed along with this example, the protocol pane may well contain
the protocol ""as yet unclassified"".
Right-click in the protocol pane and select ""categorize all uncategorized""
to fix this, and move the ==initialize== method to a new protocol called
""initialization"" (see Figure *@fig:categorizeProtocoles*).
How does Pharo know that this is the right protocol? Well, in general Pharo
can't know exactly, but in this case there is also an ==initialize== method in
the superclass, and Pharo assumes that our ==initialize== method should go in
the same protocol as the one that it overrides.
!!!!!A typographic convention.
Smalltalkers frequently use the notation ==>>== to identify the class to which a
method belongs. For example, the ==cellsPerSide== method in class ==LOGame==
would be referred to as ==LOGame >> cellsPerSide==. Just keep in mind that this
is not Smalltalk syntax exactly, but merely a convenient notation to indicate
"the instance method ==cellsPerSide== which belongs to the class ==LOGame==".
(Incidentally, the corresponding notation for a class-side method would be
==LOGame class >> someClassSideMethod==.)
From now on, when we show a method in this book, we will write the name of the
method in this form. Of course, when you actually type the code into the
browser, you don't have to type the class name or the ==>>==; instead, you just
make sure that the appropriate class is selected in the class pane.
Now let's define the other two methods that are used by ==LOGame >> initialize==.
Both of them can go in the ==initialization== protocol.
[[[language=smalltalk|caption=A constant method|label=scr:CellsPerSide
LOGame >> cellsPerSide
"The number of cells along each side of the game"
^ 10
]]]
This method could hardly be simpler: it answers the constant 10. One advantage
of representing constants as methods is that if the program evolves so that the
constant then depends on some other features, the method can be changed to
calculate this value.
As you can see there are some tabulation and empty lines. In order to keep the
same convention you can right-click on the method edit area and click on
==Format== (or use ==CMD-Shift-u== shortcut). This will format your method.
[[[language=smalltalk|caption=An initialization helper method|label=scr:NewCellAt
LOGame >> newCellAt: i at: j
"Create a cell for position (i,j) and add it to my on-screen representation at the appropriate screen position. Answer the new cell"
| c origin |
c := LOCell new.
origin := self innerBounds origin.
self addMorph: c.
c position: ((i - 1) * c width) @ ((j - 1) * c height) + origin.
c mouseAction: [ self toggleNeighboursOfCellAt: i at: j ]
]]]
Add the methods ==LOGame >> cellsPerSide== and ==LOGame >> newCellAt: at:==.
The method defined above answers a new ==LOCell==, initialized to position (i,
j) in the Matrix of cells. The last line defines the new cell's ==mouseAction==
to be the block ==[self toggleNeighboursOfCellAt: i at: j ]==. In effect, this
defines the callback behaviour to perform when the mouse is clicked. The
corresponding method also needs to be defined.
[[[language=smalltalk|caption=The callback method|label=scr:Toggle
LOGame >> toggleNeighboursOfCellAt: i at: j
i > 1
ifTrue: [ (cells at: i - 1 at: j) toggleState ].
i < self cellsPerSide
ifTrue: [ (cells at: i + 1 at: j) toggleState ].
j > 1
ifTrue: [ (cells at: i at: j - 1) toggleState ].
j < self cellsPerSide
ifTrue: [ (cells at: i at: j + 1) toggleState ]
]]]
The above method toggles the state of the four cells to the north, south, west
and east of cell (i, j). The only complication is that the board is finite, so
we have to make sure that a neighboring cell exists before we toggle its state.
Place this method in a new protocol called ==game logic==. (Right-click in the
protocol pane to add a new protocol.)
To move (re-classify) the method, you can simply click on its name and drag it
to the newly-created protocol (see Figure *@fig:dropProtocol*).
+Drag a method to a protocol>file://figures/dropProtocol.png|width=100|label=fig:dropProtocol+
To complete the Lights Out game, we need to define two more methods in class
==LOCell== to handle mouse events.
[[[language=smalltalk|caption=A typical setter method|label=scr:TypicalSetter
LOCell >> mouseAction: aBlock
^ mouseAction := aBlock
]]]
The method above does nothing more than set the cell's ==mouseAction== variable
to the argument, and then answers the new value. Any method that changes the
value of an instance variable in this way is called a ''setter method''; a method
that answers the current value of an instance variable is called a ''getter
method''.
If you are used to getters and setters in other programming languages, you might
expect these methods to be called ==setMouseAction== and ==getMouseAction==. The
Smalltalk convention is different. A getter always has the same name as the
variable it gets, and a setter is named similarly, but with a trailing ":",
hence ==mouseAction== and ==mouseAction:==.
Collectively, setters and getters are called ''accessor methods'', and by
convention they should be placed in the ==accessing== protocol. In Smalltalk,
all instance variables are private to the object that owns them, so the only way
for another object to read or write those variables is through accessor methods
like this one. In fact, the instance variables can be accessed in subclasses
too.
Go to the class ==LOCell==, define ==LOCell >> mouseAction:== and put it in the
==accessing== protocol.
Finally, we need to define a method ==mouseUp:==. This will be called
automatically by the GUI framework if the mouse button is released while the
cursor is over this cell on the screen.
[[[language=smalltalk|caption=An event handler|label=scr:MouseUp
LOCell >> mouseUp: anEvent
mouseAction value
]]]
Add the method ==LOCell >> mouseUp:== and then ""Categorize automatically""
the methods.
What this method does is to send the message ==value== to the object stored in
the instance variable ==mouseAction==. Recall that in ==LOGame >> newCellAt: i
at: j== we assigned the following code fragment (called a ''block'') to
==mouseAction==:
==[self toggleNeighboursOfCellAt: i at: j ]==
Sending the ==value== message causes this block to be evaluated, and
consequently the state of the cells will toggle.
!!Let's try our code
That’s it: the Lights Out game is complete!
If you have followed all of the steps, you should be able to play the game,
consisting of just 2 classes and 7 methods.
In a Playground, type ==LOGame new openInWorld== and ""Do it"" .
The game will open, and you should be able to click on the cells and see how it
works.
Well, so much for theory... When you click on a cell, a notifier window called
the PreDebugWindow window appears with an error message like we see on Figure
*@fig:doesNotUnderstood*! It says ==MessageNotUnderstood: LOGame >>
toggleState==.
+There is a bug in our game when a cell is clicked!>file://figures/doesNotUnderstood.png|width=100|label=fig:doesNotUnderstood+
What happened? To find out, let’s use one of Smalltalk’s more powerful tools:
the debugger.
Click on the ""Debug"" button in the notifer window.
The debugger will appear. In the upper part of the debugger window you can see
the execution stack, showing all the active methods. Selecting any one of them
will show, in the middle pane, the Smalltalk code being executed in that method,
with the part that triggered the error highlighted.
Click on the line labeled ==LOGame >> toggleNeighboursOfCellAt: at:== (near the
top).
The debugger will show you the execution context within this method where the
error occurred (see Figure *@fig:debuggerHighlight*).
+The debugger, with the method ==toggleNeighboursOfCell:at:== selected>file://figures/debuggerHighlight.png|width=85|label=fig:debuggerHighlight+
At the bottom of the debugger are two small inspector windows. On the right, you
can inspect the object that is the receiver of the message that caused the
selected method to execute, so you can look here to see the values of the
instance variables. On the left you can inspect an object that represents the
currently executing method itself, so you can look here to see the values of the
method's parameters and temporary variables.
Using the debugger, you can execute code step by step, inspect objects in
parameters and local variables, evaluate code just as you can in a playground,
and, most surprisingly to those used to other debuggers, change the code while
it is being debugged! Some Smalltalkers program in the debugger almost all the
time, rather than in the browser. The advantage of this is that you see the
method that you are writing as it will be executed, with real parameters in the
actual execution context.
In this case we can see in the first line of the top panel that the
==toggleState== message has been sent to an instance of ==LOGame==, while it
should clearly have been an instance of ==LOCell==. The problem is most likely
with the initialization of the cells matrix. Browsing the code of
==LOGame >> initialize== shows that ==cells== is filled with the return values
of ==newCellAt: at:==, but when we look at that method, we see that there is no
return statement there! By default, a method returns ==self==, which in the case
of ==newCellAt: at:== is indeed an instance of ==LOGame==.
Close the debugger window. Add the expression ==^ c== to the end of the method
==LOGame >> newCellAt:at:== so that it returns ==c==.
[[[language=smalltalk|caption=Fixing the bug.|label=scr:BugFix
LOGame >> newCellAt: i at: j
"Create a cell for position (i,j) and add it to my on-screen representation at the appropriate screen position. Answer the new cell"
| c origin |
c := LOCell new.
origin := self innerBounds origin.
self addMorph: c.
c position: ((i - 1) * c width) @ ((j - 1) * c height) + origin.
c mouseAction: [ self toggleNeighboursOfCellAt: i at: j ].
^ c
]]]
Recall from Chapter *: A Quick Tour of Pharo>../PharoTour/PharoTour.pillar@cha:tour*
that the syntax to return a value from a method in Smalltalk is ==^==.
Often, you can fix the code directly in the debugger window and click
==Proceed== to continue running the application. In our case, because the bug
was in the initialization of an object, rather than in the method that failed,
the easiest thing to do is to close the debugger window, destroy the running
instance of the game (with the halo), and create a new one.
Do: ==LOGame new openInWorld== again.
Now the game should work properly... or nearly so. If we happen to move the
mouse between clicking and releasing, then the cell the mouse is over will also
be toggled. This turns out to be behavior that we inherit from
==SimpleSwitchMorph==. We can fix this simply by overriding ==mouseMove:== to do
nothing:
[[[language=smalltalk|caption=Overriding mouse move actions|label=scr:MouseMove
LOGame >> mouseMove: anEvent
]]]
Finally we are done!
!!Saving and sharing Pharo code
Now that you have the Lights Out game working, you probably want to save it
somewhere so that you can share it with your friends. Of course, you can save
your whole Pharo image, and show off your first program by running it, but your
friends probably have their own code in their images, and don't want to give
that up to use your image. What you need is a way of getting source code out of
your Pharo image so that other programmers can bring it into theirs.
@@note We'll be discussing the various ways to save and share code in a future chapter, Chapter *: Sharing Code and Source Control>./SharingCode/SharingCode.pillarr@cha:sharingCode*. For now, here is a brief overview of some of the available methods.
The simplest way of doing this is by "filing out" the code. The right-click
menu in the Package pane will give you the option to ==File Out== the whole of
package ==PBE-LightsOut==. The resulting file is more or less human readable,
but is really intended for computers, not humans. You can email this file to
your friends, and they can file it into their own Pharo images using the file
list browser.
Right-click on the ==PBE-LightsOut== package and file out the
contents (see Figure *@fig:fileOut*).
+File Out our PBE-LightsOut>file://figures/fileOut.png|width=75|label=fig:fileOut+
You should now find a file named ==PBE-LightsOut.st== in the same folder on disk
where your image is saved. Have a look at this file with a text editor.
+Import your code with the file browser>file://figures/fileBrowser.png|width=100|label=fig:fileBrowser+
Open a fresh Pharo image and use the ==File Browser== tool (==Tools \-\-> File
Browser==) to file in the ==PBE-LightsOut.st== fileout (see Figure
*@fig:fileBrowser*) and ==fileIn==. Verify that the game now works in the new
image.
!!!Monticello packages
@fstApp:Monticiello
Although fileouts are a convenient way of making a snapshot of the code you have
written, they are decidedly "old school". Just as most open-source projects find
it much more convenient to maintain their code in a repository using SVN or
Git, so Pharo programmers find it more convenient to manage their code
using Monticello packages. These packages are represented as files with names
ending in ==.mcz==. They are actually zip-compressed bundles that contain the
complete code of your package.
Using the Monticello package browser, you can save packages to repositories on
various types of server, including FTP and HTTP servers. You can also just write
the packages to a repository in a local file system directory. A copy of your
package is also always cached on your local disk in the ==package-cache==
folder. Monticello lets you save multiple versions of your program, merge
versions, go back to an old version, and browse the differences between
versions. In fact, Monticello is a distributed revision control system. This
means it allows developers to save their work on different places, not on a
single repository as it is the case with CVS or Subversion.
You can also send a ==.mcz== file by email. The recipient will have to place it in
her ==package-cache== folder; she will then be able to use Monticello to browse and
load it.
Open the Monticello browser from the World menu (see Figure *@fig:monticiello*).
+Monticiello browser>file://figures/monticiello.png|width=100|label=fig:monticiello+
In the right-hand pane of the browser is a list of Monticello repositories,
which will include all of the repositories from which code has been loaded into
the image that you are using.
At the top of the list in the Monticello browser is a repository in a local
directory called the package cache, which caches copies of the packages that you
have loaded or published over the network. This local cache is really handy
because it lets you keep your own local history. It also allows you to work in
places where you do not have internet access, or where access is slow enough
that you do not want to save to a remote repository very frequently.
!!!Saving and loading code with Monticello
On the left-hand side of the Monticello browser is a list of packages that have
a version loaded into the image. Packages that have been modified since they
were loaded are marked with an asterisk. (These are sometimes referred to as
dirty packages.) If you select a package, the list of repositories is restricted
to just those repositories that contain a copy of the selected package.
Add the ==PBE-LightsOut== package to your Monticello browser using the
""+ Package"" button and type ==PBE-LightsOut==.
!!!SmalltalkHub: a Github for Pharo
We think that the best way to save your code and share it is to create an
account for your project on a SqueakSource server. SmalltalkHub is like GitHub:
it is a web front-end to a HTTP Monticello server that lets you manage your
projects. There is a public server at
*http://www.smalltalkhub.com/>http://www.smalltalkhub.com/*.
In order to be able to use SmalltalkHub you will need an account. Open the site
in your browser. Then, click on the
""Join"" link and follow the instructions to create a new account. Finally,
""login"" to your account.
+Join and login at SmalltalkHub>file://figures/smalltalkhub-join.png|width=100|label=fig:join+
Click on the ""+ New project"" to create a new project. Fill in the information
requested and click ""Register project"" button. You will be sent to your
profile page, on the right side you will see the list of your projects and
projects you watch by other coders. Click on the project you just created.
Under ""Monticello registration"" title label you will see a box containing a
smalltalk message similar to
[[[lang=Smalltalk|caption=A repository pattern|label=scr:RepositoryPattern
MCHttpRepository
location: 'http://www.smalltalkhub.com/mc/UserName/ProjectName/main'
user: 'YourName'
password: 'Optional_Password'
]]]
Copy those contents.
Go back to Pharo.
Once your project has been created on SmalltalkHub, you have to tell your Pharo
system to use it.
With the ==PBE-LightsOut== package selected, click the ""+Repository"" button in
the Monticello browser.
You will see a list of the different Repository types that are available. To add
a SmalltalkHub repository select ==smalltalkhub.com==. You will be presented
with a dialog in which you can provide the necessary information about the
server. You should paste the code snippet you have copied from SmalltalkHub (see
Figure *@fig:createRepository*). This message tells Monticello where to find
your project online. You can also provide your user name and password. If you do
not, then Pharo will ask for them each time you try to save into your online
repository at SmalltalkHub.
+Create your first repository on smalltalkHub>file://figures/createRepository.png|width=100|label=fig:createRepository+
Once you have accepted, your new repository should be listed on the right-hand
side of the Monticello browser.
Click on the ""Save"" button to save a first version of your Lights Out game on
SmalltalkHub. Don't forget to put a comment so that you, or others, will have an
idea of what contains your commit for the future (see Figure
*@fig:firstCommit*).
+Do your first commit>file://figures/firstCommit.png|width=60|label=fig:firstCommit+
To load a package into your image, you must first select a particular version.
You can do this in the repository browser, which you can open using the button
or the right-click menu. Once you have selected a version, you can load it onto
your image.
Open the ==PBE-LightsOut== repository you have just saved.
Monticello has many more capabilities, which will be discussed in depth in
Chapter *: Sharing Code and Source Control>./SharingCode/SharingCode.pillarr@cha:sharingCode*.
You can also look at the online documentation for Monticello at
*http://www.wiresong.ca/Monticello/>http://www.wiresong.ca/Monticello/*.
If you are already familiar with Git and Github there is a chapter about how to
use Git with Pharo in the online book Enterprise Pharo: A Web Perspective at
*http://files.pharo.org/books/enterprisepharo/>http://files.pharo.org/books/enterprisepharo/*.
If you have not done so already its recommended to download the entire book,
because it contains a lot of valuable information about Pharo and its third
party libraries.
!!Chapter summary
In this chapter you have seen how to create packages, classes and methods. In
addition, you have learned how to use the System browser, the inspector, the
debugger and the Monticello browser.
-Packages are groups of related classes.
-A new class is created by sending a message to its superclass.
-Protocols are groups of related methods inside a class.
-A new method is created or modified by editing its definition in the browser and then accepting the changes.
-The inspector offers a simple, general-purpose GUI for inspecting and interacting with arbitrary objects.
-The browser detects usage of undeclared variables, and offers possible corrections.
-The ==initialize== method is automatically executed after an object is created in Pharo. You can put any initialization code there.
-The debugger provides a high-level GUI to inspect and modify the state of a running program.
-You can share source code by filing out a package, class or method.
-A better way to share code is to use Monticello to manage an external repository, for example defined as a SmalltalkHub project.