From 2cbbef846c63fb5868944771cce110d673f97ddc Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Fri, 24 Apr 2026 21:39:40 -0400 Subject: [PATCH] Clarify MemoryGame code explanations --- chapter-04/contents.texinfo | 93 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/chapter-04/contents.texinfo b/chapter-04/contents.texinfo index 97ab44c..99af5c5 100644 --- a/chapter-04/contents.texinfo +++ b/chapter-04/contents.texinfo @@ -763,29 +763,32 @@ 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' @@ -793,18 +796,18 @@ of button: 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, @@ -812,14 +815,14 @@ 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 @@ -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. @@ -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 | @@ -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 | @@ -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} @@ -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 @@ -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 @@ -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, @@ -976,40 +977,37 @@ 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, @@ -1017,9 +1015,8 @@ resist code evolution in the card protocol: 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)}