Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 45 additions & 48 deletions chapter-04/contents.texinfo
Original file line number Diff line number Diff line change
Expand Up @@ -763,63 +763,66 @@ of cards is presented to the user with a common neutral color; each
card has its own color, hidden at game startup. The user must find the
cards sharing the same color. When the user clicks on a card, its
color is revealed; the card with the matching color must be
found. When a pair of cards is found, these cards are not playable
anymore; if not, the cards' colors are hidden again. Two morphs are
used in the design of the game: a kind of @class{SystemWindow} and a
kind of @class{PluggableButtonMorph}. The complete source is presented
found. When a pair of cards is found, the pair is no longer playable;
if not, the cards' colors are hidden again, or @emph{flipped}. Two
kinds of morphs are used in the design of the game: a @class{SystemWindow}
and a @class{PluggableButtonMorph}. The complete source is presented
in the appendix @ref{Memory Game v1} of the book. We will not present
every part of the code design, but we will focus on the illustrative
ones regarding the topic of this chapter. The game is started with:
ones regarding the topic of this chapter.

The game is started with:

The game is started with
@smalltalkExample{MemoryGameWindow new openInWorld}

@figure{Memory color game, ch04-memoryGame,8}

@cindex widget @subentry button

@unnumberedsubsec The Card
@unnumberedsubsec The Cards

What are the attributes we want a card to have? We need a card to have
a specific @emph{card color} and a status flag to indicate if we are
@emph{done} with the card. The card is a morph able to paint itself
with a color. It reacts to user clicks to flip itself between its own
@emph{card color} and the common neutral color. What we want is a kind
of button:
@emph{done} with the card, meaning we have found a matching pair.
The card is a morph able to paint itself with a color. It reacts to user
clicks to flip itself between its @emph{card color} and the common neutral
color.

What we want is a kind of button:

@smalltalkExample{PluggableButtonMorph subclass: #MemoryCard
instanceVariableNames: 'cardColor done'
classVariableNames: ''
poolDictionaries: ''
category: 'MemoryGameV1'}

The default color, common to all the cards, is white:
The default color, common to all cards, is white:

@smalltalkMethod{defaultColor,
@return{} Color white}

It knows to be flipped when its @smalltalk{cardColor} is used as the
A card knows it is flipped when its @smalltalk{cardColor} is the
color of the button:

@smalltalkMethod{isFlipped,
@return{} color = cardColor }

And the card can flip between this common color and its own
And the card can flip between the default color and its own
@smalltalk{cardColor}:

@smalltalkMethod{flip,
color := self isFlipped ifTrue: [self defaultColor] ifFalse: [cardColor].
self redrawNeeded}

@smalltalk{color} is used to paint the button; when adjusting it, we
send the message @msg{redrawNeeded} to the button to force its redraw.
send the message @msg{redrawNeeded} to the button to force it to redraw.

@cindex widget @subentry window

@unnumberedsubsec The Board

What are the attributes of the game board? It knows about the
@emph{playground} set with a specific @emph{size} where the
@emph{playground}, set to a specific @emph{size}, where the
@emph{cards} are presented to the user. It communicates messages
through its @emph{status bar} and it knows if the user is
@emph{playing} or not. The game is presented in a window with all
Expand All @@ -831,8 +834,8 @@ these attributes:
poolDictionaries: ''
category: 'MemoryGameV1'}

The board presents a toolbar, its playground, and status bar in the
window column @smalltalk{layoutMorph}:
The board presents a toolbar, its playground, and a status bar in a
window using a column @smalltalk{LayoutMorph}:

@smalltalkMethod{initialize,
super initialize.
Expand All @@ -852,7 +855,7 @@ arranged into several rows.
@subsubsection Installing the Game

@cuis{} does not come with any notion of a toolbar, but it is fairly easy to
create one with a row layout and buttons:
create one with a row @smalltalk{LayoutMorph} and some buttons:

@smalltalkMethod{installToolbar,
| toolbar button |
Expand All @@ -875,11 +878,10 @@ applies with the ``Stop'' button -- not shown here. The toolbar height
the toolbar is added to the window with the appropriate specification
@smalltalk{LayoutSpec new useMorphHeight}.

The cards are installed in several rows in the @smalltalk{playground},
previously emptied. We remember about each card in a special
@smalltalk{cards} array we can access with x and y coordinates, also
the position of a card in the playground. The colors to be used are
randomly chosen and arranged in:
We first clear the @smalltalk{playground}, then install the cards in several
rows. We remember each card's position which we can access via x and y coordinates
by storing the @smalltalk{cards} in a special @class{Array2D} array. The colors to
be used are randomly shuffled and then assigned in the inner @msg{to:do:} block:

@smalltalkMethod{installCards,
| colors row |
Expand All @@ -896,9 +898,9 @@ colors := self distributeColors shuffled.
cards at: x@@y put: card ].
playground addMorph: row ]}

We make the card interactive; it is a button. When clicked, the
message @msg{flip:} is sent to the game window with the argument the
position in the @smalltalk{cards} array and playground:
We make the card interactive by using the @class{PluggableButtonMorph}'s @smalltalk{model:action:actionArgument:} selector. When clicked, the
message @msg{flip:} is sent to the game window with the card's x and y position
in the @smalltalk{cards} array and playground:

@smalltalkExample{MemoryCard model: self action: #flip: actionArgument: x@@y}

Expand All @@ -913,7 +915,7 @@ with a given morph.} the clicked card:
| flippedCards |
(cards at: position) flip; lock.}

Then it detects if all the flipped cards share the same color. To do so, we do
Then it detects if all the flipped cards share the same color. To do so, we perform
a clever trick in Smalltalk: we collect all the colors of the flipped cards,
then convert the collection of colors into a @class{Set} instance; all duplicated
colors are removed. If the size of the resulting set is not 1, it means the cards
Expand Down Expand Up @@ -947,14 +949,13 @@ and update the game status:

@smalltalkExample{
self isGameWon ifTrue: [
self message: 'Congratuluations@comma{} you finished the game!' bold red.
self message: 'Congratulations@comma{} you finished the game!' bold red.
playing := false] ]}

@subsubsection Messages to the User

During the game logic, at several occurrences, we informed the user through
messages. The messages are printed in the status bar set at initialization
time:
The user is kept informed during gameplay by displaying messages in the status bar.
This is set up at initialization time in the following method:

@smalltalkMethod{installStatusBar,
statusBar := TextParagraphMorph new
Expand All @@ -966,7 +967,7 @@ statusBar := TextParagraphMorph new
self addMorph: statusBar layoutSpec: LayoutSpec new useMorphHeight.
self message: 'Welcome to '@comma{} 'Memory Game' bold}

Its companion method to write a new text message just updates the
Its companion method @msg{message:} writes a new text message by updating the
contents of the @class{TextParagraphMorph} instance:

@smalltalkMethod{message: aText,
Expand All @@ -976,50 +977,46 @@ statusBar contents: aText ;
@cindex text
@cindex widget @subentry text

A message sent to the status bar can be more than a plain string; it can be a
A message sent to the status bar can be more than just a plain string; it can be a
@class{Text} instance with styling attributes. To do so, we send specific
messages to a string. For example, @smalltalk{'hello' bold} converts the 'hello'
string to a @class{Text} set with a bold style.
string to a @class{Text} instance set to a bold style.

Examples of styling:

@smalltalkExample{'Hello' red bold.
'Hello ' italic, ' my love' red bold.}

To discover more messages, browse the method categories @label{text
To discover more message style attributes, browse the method categories @label{text
conversion ...} of the @class{CharacterSequence} class.

@subsubsection Access and Test

In the core logic of the game, we accessed the flipped cards in the
playground. It is a matter of selecting the cards both @emph{not
done} and @emph{flipped}:
In the core logic of the game, we access the flipped cards in the
playground by selecting the cards both @emph{not done} and @emph{flipped}:

@smalltalkMethod{flippedCards,
@return{} cards elements select: [:aCard | aCard isDone not and: [aCard isFlipped] ]}

The @class{Array2D} instance of the @smalltalk{cards} variable offers
access to its cells with x and y coordinates; however, it does not
offer the full range of the @class{Collection} protocol, and
particularly the @method{select:} method. Nevertheless, its
underlying @smalltalk{elements} attribute is an @class{Array}, part of
the @class{Collection} hierarchy; we use it to get the full power of
the @class{Collection} protocol. We proceed the same way to select the done cards:
offer the full range of the @class{Collection} protocol, particularly the @method{select:} method. Nevertheless, its underlying @smalltalk{elements} attribute is an @class{Array}, which @emph{is}
part of the @class{Collection} hierarchy; we use it to get the full power of
the @class{Collection} protocol. We proceed the same way to select the @emph{done} cards:

@smalltalkMethod{doneCards,
@return{} cards elements select: #isDone}

And undone cards are selected by a subtraction operation, prone to
And undone cards are selected by a removal operation, prone to
resist code evolution in the card protocol:

@smalltalkMethod{undoneCards,
@return{} cards elements asOrderedCollection
removeAll: self doneCards;
yourself}

In the core logic of the game, we test if the game is won; it is a
matter of testing if all the cards @emph{are done}. In that case, this
count is equal to the number of cards in the game:
In the core logic of the game, we test if the game is won by testing if all the cards
@emph{are done}. In that case, this count is equal to the number of cards in the game:

@smalltalkMethod{isGameWon,
@return{} (cards elements select: #isDone) size = (size x * size y)}
Expand Down
Loading