Lab #8
Building a GUI in SmallTalk

What We're Gonna Do:

In this lab, you will learn to open an ApplicationWindow in VisualWorks, and to draw some basic graphics items like a polygon or a circle on it, in different color!

Download CardGame

This lab is a continuation of Lab 4&5. First, after you set up your setting for VisualWork, file-in your code for Lab 4&5. If you did not finish that lab or you are not satisfied with what you did before, you can also use the solution of that lab given on the Web. In order to make it runnable, you need to file-in Sample.st.

In the Player class, add a class method demo.

demo
        | players gameCards winners |
        players := Set new.
        4 timesRepeat: [ players add: Player new ].
        gameCards := CardDeck new.
        gameCards shuffle.
        [ winners := players select: [ :each | each points between: 18 and: 21 ].
        winners isEmpty and: [ gameCards isEmpty not ]]
        whileTrue:
                [ players do:
                [ :each | each points < 21 ifTrue: [ each take: gameCards next ]]].
        winners do: [ :player | player showHand ].
In the Workspace, type "Player demo" and do it. You should be able to see the cards in the winners' hands be displayed in Transcript.

Draw a Club

In the Card class, make a new class method protocol drawing . Create three methods in this protocol as below:


drawBorderAt: aPoint on: gc 
        | border |
        border := OrderedCollection new.
        border add: 0 @ 0; add: 40 @ 0; add: 40 @ 30; add: 0 @ 30; add: 0 @ 0.
        gc displayPolyline: border at: aPoint.

drawRank: aRank at: aPoint on: gc 
        | symbol |
        aRank < 11
                ifTrue: [aRank = 1
                                ifTrue: [symbol := 'A']
                                ifFalse: [symbol := aRank printStringRadix: 10]]
                ifFalse: [aRank = 11
                                ifTrue: [symbol := 'J']
                                ifFalse: [aRank = 12
                                                ifTrue: [symbol := 'Q']
                                                ifFalse: [symbol := 'K']]].
        gc displayString: symbol at: aPoint x + 25 @ (aPoint y + 20)

clubDraw: aRank at: aPoint on: gc 
        | circle shape |
        Card drawBorderAt: aPoint on: gc.
        shape := OrderedCollection new.
        shape add: 10 @ 12; add: 12 @ 25; add: 8 @ 25.
        circle := Circle center: aPoint x + 10 @ (aPoint y + 10) radius: 3.
        circle displayFilledOn: gc.
        circle setCenter: aPoint x + 8 @ (aPoint y + 15) radius: 3.
        circle displayFilledOn: gc.
        circle setCenter: aPoint x + 12 @ (aPoint y + 15) radius: 3.
        circle displayFilledOn: gc.
        gc displayPolygon: shape at: aPoint.
        Card
                drawRank: aRank
                at: aPoint
                on: gc

Among these methods, drawBorderAt: on: is to draw a rectangle as border of a card at a position on a GraphicsContext . Method drawRank: at: on: is to display the rank of a card at a postion on a GraphicsContext. There are four special ranks: 1, 11, 12, 13 which should be displayed as A, J, Q, K, respectively.

Usually, there are two ways to draw something on a GraphicsContext . One is to create an object, e.g. a circle, and pass the GraphicsContext as an argument to this object to draw itself. In this example,
circle displayFilledOn: gc.
Another way is to draw a graphics item on that GraphicsContext directly, as in this example
gc displayPolygon: shape at: aPoint.

Most simple graphics items, like a circle or a polygon, can be displayed in both those ways above. However, for more sophisticated graphics shapes, you can only use the former way.

Now, you can test if you can draw a Club by do the following code in Workspace:

        | gc |
        gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
        Card clubDraw: 1 at: 10@10 on:gc
Attention: Close the display window for cards after you get what you want, otherwise it will cause some trouble in the future. Sometime nothing appear in the window at the first time you do this code. Just close that ScratchWindow and do it once more WITHOUT CLOSING THE WINDOW. It will work.

Draw a Diamond

Now, it's time to draw a Diamond. To make it look better, it should be red. Fortunately, it's not difficult to change painting color in VisualWorks. You can create a class method diamodDraw: at: on: for Card class as below:

diamondDraw: aRank at: aPoint on:gc
        | shape |

        Card drawBorderAt: aPoint on: gc.
        shape := OrderedCollection new.
        shape add: 10 @ 5; add: 15 @ 15; add: 10 @ 25; add: 5 @ 15.
        gc setPaintToColor: ColorValue red.
        gc displayPolygon: shape at: aPoint.
        gc setPaintToColor: ColorValue black.
        Card
                drawRank: aRank
                at: aPoint
                on: gc

Now, you write several lines in Workspace to test how it works.

Your Job

It's your turn now. You write two class method for Card class: spadeDraw: at: on: and heartDraw: at: on: , and test them in Workspace.

heartDraw: aRank at: aPoint on: gc

        Card drawBorderAt: aPoint on: gc.
        "you draw a heart in red here."
        Card
                drawRank: aRank
                at: aPoint
                on: gc

spadeDraw: aRank at: aPoint on: gc

        Card drawBorderAt: aPoint on: gc.
        "you draw a spade in black here."
        Card
                drawRank: aRank
                at: aPoint
                on: gc
Here is the geometrical design for those cards. You should draw your Spade and Heart in the rectangle (5, 5) to (15, 25).

Hint: a Heart can be drawn as one diamond and two circles, and a spade is a reversed heart with a handle.

Show Cards in Players' Hands

In Player class, create a class method newScratchWindowAt: size:, in a new protocol if you wish, to generate an ApplicationWindow.
newScratchWindowAt: aPoint size: anExtent 
        | aScratchWindow ebCollection |
        aScratchWindow := ApplicationWindow new label: 'Card Window'.
        ebCollection := ExamplesBrowser allInstances select: [:eb | eb builder notNil].
        ebCollection isEmpty
                ifTrue: 
                        [aScratchWindow sensor skipNextDamage.
                        
                        aScratchWindow openIn: (aPoint extent: anExtent)]
                ifFalse: [ebCollection first openScratchWindowOfSize: anExtent].
        ^aScratchWindow

In the inquiries protocol of Player class, build an instance method displayHand: as below:

displayHand: aPoint
        | gc x |

        gc := (Player newScratchWindowAt: aPoint size: 400@80) graphicsContext.
        x := 5.
        cards do: [:eachCard | 
                        (eachCard suit = #spade)
                        ifTrue: [Card spadeDraw: (eachCard rank) at: ( x @ 5) on: gc].
                        (eachCard suit = #heart)
                        ifTrue: [Card heartDraw: (eachCard rank) at: ( x @ 5) on: gc].
                        (eachCard suit = #diamond)
                        ifTrue: [Card diamondDraw: (eachCard rank) at: ( x @ 5) on: gc].
                        (eachCard suit = #club)
                        ifTrue: [Card clubDraw: (eachCard rank) at: ( x @ 5) on: gc].
                        x := x + 50]

Then, change the last line of demo of Player class from:

        winners do: [ :player | player showHand ].
to
        y := 100.
        winners do: [ :player | player displayHand: 50@y.
                                        y := y + 150  ].
Sure, you need to declare y in your temporary variable list.

In the Workspace, do Player demo. Do you see one or more windows on the screen with cards in those? Remember to close those windows before you want to execute next time.

Your Job Again

You write something to show how many points each winner has by display a string at the bottom of "Card Window". You can refer to the drawRank: at: on: method in Card class to see how to display a string in a window.

Turning in the Code

OK, that's all for today. Go to the system Browser and select the category holding the CardGame classes. Choose the middle-button menu item File Out and type a file name (or the default category name) when prompted. Mail this file to your appropriate TA. (Don't send Sample classes).

(1:30-3)
cat output.txt CardGame.st | mail -s "lab 8 - YOUR NAME" joita@cc.gatech.edu
OR (3-4:30)
cat output.txt CardGame.st | mail -s "lab 8 - YOUR NAME" jyan@cc.gatech.edu
OR (4:30-6)
cat output.txt CardGame.st | mail -s "lab 8 - YOUR NAME" smk@cc.gatech.edu

Please quit from VisualWorks before you logout.