What this is about...

This blog is about how I spend some of my time with my computers.

Various topics on Computing, Operating Systems, Programming Languages, Computer Games, Internet and Communications

Tuesday, March 2, 2010

Learning Java (& SWT & Eclipse) by example: A simple game of patience (solitaire)

As an exercise in Eclipse, Java, SWT and Model-View-Controller, I programmed the following patience (or solitaire, if you prefer) card game that was shown to me by a high-school friend back in 1977-78!
First, let me say that I did not “design” the program and then built it based on some formal design specs. I tried to use an “unfolding” process (like the method Christopher Alexander uses in his timeless way to build); I started by some general decision on concepts like Model-View-Controller, or the auto-play mode. I also made some software decisions i.e. to use Java, Eclipse and the SWT toolkit. Then, with the knowledge of how the game worked, I started to write the classes and the glue code. My programming method consisted of changing the classes in an iterative process, adding functionality, refining them and many times altering them substantially, until they “stabilized” to this working version. It is also natural that the data elements and methods either reflect some physical element or process, or they are there to support one, because they reflect a “real'” game as I play it. The classes are not “perfectly coded,” nor do they have support for “future possibilities;” they were not created to support multiple future card games (although with some minor alterations they could). I specifically created them with this program in mind. I have not “factored out” reusable parts etc. this can always happen later on.
This process has made the “feel” of the program more organic. I tried some things, refined them, repaired them, created & deleted stuff, and so on. This version is not “final” in any sense, and its structure is based too strongly on the MVC pattern.
I decided to post the code in this blog for everyone to see and comment so that I can investigate options and  improve the program. Ultimately, I want to move on to Scala with actors, traits, and functional programming. I also want to investigate C++ programming under Ubuntu.
Since I created this program (and, in a way, the game), if you decide to use any parts of it, or recreate the game (or part of it) again please mention this blog and my efforts. I will gradually add/change the program to use resources (instead of external .gif files) and, perhaps, re-do it in C++ and some cross-platform IDE.
Before I describe the game itself, I present a list of the things I wanted the application to do and how I wanted it to do them (basic UI/functionality):
  • It should work in a manual, user-action mode but with as little ”mousing around” as possible. I though of implementing card dragging, but decided to keep that as an exercise for the next version. Most of the user actions are done by double clicking on widgets and buttons.
  • It should have an auto-play mode, where it could run any number of games for statistical purposes. The game window should update during autoplay so the user can see the moves, and the speed of the game should be changeable (using a slider control) by the user. 
  • It should have a window with the rules of the game, that the user can activate by pressing a button.
  • It should show the game’s statistics in a separate window floating over or under the game window.
  • If possible, it should not use threads, as this was my first exercise and I wanted the code to be as simple as possible.
  • It should not pause to shuffle the cards at the beginning of each game. With the exception of when the game runs for the first time, I shuffle while the user (or the system) plays the game without using threads, as follows: I use 2 decks and swap them in the beginning of a game. While the game progresses, in the main play loop, I send a shuffle message to the second (off-line) deck asking it to swap 2 random card locations n number of times (where n is presently between 1 and 3, but that can change). After the game is over, I have sent the shuffle message to the off-line deck a minimum of 52 times (if in autoplay mode and no triplets are removed from the columns), each time asking it to swap from 1 to 3 pairs of cards. The worst case scenario (in autoplay mode) is 52 card swaps, and the best is many thousands (as the game loops waiting for user input, or as the game removes triplets and deals again and again). Before the game starts, I make the off-line deck the playing deck and so on… I think this way I always start a game with a sufficiently shuffled deck without making the user wait.
Future additions:
  • Code to automatically detect a “stuck” game and recover.
  • Code to implement the dragging and dropping  of cards 
  • Some kind of animation when a card moves from one location to another.
So, the game itself is quite simple, here are the rules:
  1. Shuffle the deck.
  2. Start dealing cards in 7 columns, row by row: i.e. C1R1, C2R1, C3R1, … C7R1, C1R2, C2R2, … C7R2
  3. From the 3rd row and above, and for every dealt column, if the sum of the bottom 3 cards equals 10, 20 or 30, remove the cards and put them back to the bottom of the deck. Do the same if the bottom 2 cards and the top card also sum up to 10, 20 or 30. Do the same if the bottom 1 card and the top 2 cards add up. You must always remove 3 cards, never more or less, and from the locations listed above. Let’s call the combos B3, T1B2, and T2B1. No other combination is allowed. Not the top 3 cards, not more that 3 cards, even if they sum up to the 10/20/30 amount.
  4. If, after removing a triplet, there are still other valid triplets in that column, you can remove them too (if you want, I always do) at any time before dealing again to that column. If you missed a triplet and after dealing you realize that you should have moved the cards to the deck, sorry, you cannot take that back!
  5. If, after removing a triplet, the column has no cards left, that column is not dealt from the deck again until the end of the game. This way the number of columns can decrease during the game, and you get closer to winning!
  6. If you can find no more valid triplets to remove (from any column), you deal to the next (non-empty) column.
  7. You win if you manage to migrate all cards from all columns back to the deck.
  8. You lose if you have no more cards to deal from the deck and no triplets can be removed anymore from any column (or the game is “stuck”, see Note 1 below).
  9. The cards score points according to the following numeric values (for the addition): Ace is 1, two is 2, three is 3, etc, ten is 10, Jack is 10, Queen is 10, King is 10.
  10. That’s it.
Note 1: it is possible for this game to reach a point where the player can repeat the same moves but the game cannot produce an outcome. This is what I call a “stuck game”, and is equivalent to losing because the cards cannot return to the deck (game lasts indefinitely, unless you stop it).
Here is a screenshot of the main screen at the beginning of a game under the Windows OS:
Main Game Screen
and again under Ubuntu:
Ubuntu Game Window
Note 2: I downnoaded the card images from the web. Specifically, from here:
As things are now, you will need the card images as external files in a subdirectory named resources. It should be located under the directory where the compiled files are located.

The deck is called the Doods deck by Katzmiff (2005). It can be used under the GNU License (it was in the rar file). Same license holds for my program, as well.
Let me explain a bit the graphical format of the window: On the left side of the screen I deal the cards on 7 columns. Right below those you can see 3 buttons in each column representing the 3 possible card-removal combinations: B3 (bottom 3 cards), T2B1 (top 2 bottom 1 card), and T1B2 (top 1 bottom 2 cards). The user can press any one of these 21 buttons to indicate to the program his/her wish. If the cards can be removed, they will be put back to the bottom of the deck (which stands to the right of the seventh –last- column). If not, nothing happens no matter how much you click!
On the right side of the window, there are some additional buttons and controls:
  1. A deal button. If pressed, a card is dealt to the next appropriate column. Obviously, empty columns will be skipped, and the column that will be dealt next is indicated by a small multi-colored square at the bottom of the window under the buttons.
    The player can also use the following other methods to deal a card from the deck: double-click anywhere on the green area of the table where no other object is located, or, double-click on the deck (which is between the last column and the deal button). Under the deck, the player can see the remaining number of cards.
  2. An auto-play checkbox. If selected, the program will go into auto-play mode where it will play consecutive games endlessly while updating the statistics window (more on that below).
    There are 2 additional controls that influence the auto-play behaviour: a slider with which the user can slow down the updating of the window, and a text entry in which the user can input a specific number of games to be played before checking auto-play. The game will gray-out the text box and count down the number.
  3. A New Game button to start a new game in mid-play, and
  4. An Exit Game Button to close the application (similar to the close button of the window).
There is another window floating on the screen:
Before I go on, please go to the source code post. Copy and paste the Java code in Eclipse and have it open while reading the explanations below, so that you can see the details. For best results, use the editor facilities to collapse and expand the code (or use multiple editors to view different parts of the file).
Some preliminaries:
  1. All classes in this application are prefixed with P7_.
  2. There is only one application thread. I deliberately tried to avoid any additional sync logic in order to keep the program as simple as possible for this initial version. Of course, the SWT and other Java system threads are still operating behind the scenes.
  3. I consciously tried to limit as much as possible object proliferation. I did this because I wanted as much speed as possible and, in my mind, since most identifiers in Java are references to objects, the more one uses the new keyword, the more work for the garbage collection thread, loading the JVM with work. 
  4. The program reads the card and other images from external .gif files that must be located in the subdirectory held in the following String variables in the P7_FixedElements class:
    (i) PileIndicatorFileNameWindows
    (ii) CardImageFilePrefixWindows[] 
    (iii) PileIndicatorFileNameUnix
    (iv) CardImageFilePrefixUnix[]
    One of the future changes will be to create a package that will contain the “resources” so that there will be no need for separate files and/or directories (like netbeans does).
  5. In the program, I use the term Pile to refer to a column of cards. The two terms are interchangeable in this text.
What follows is a list of the classes and explanations on their function.
  • There are two global enums, P7_Suit, and P7_Rank for the obvious usage.
  • I have defined a class called P7_FixedElements as a central repository to create & hold the following global-like identifiers:

    (i) the SWT elements of the UI,
    (II) the game related constants & variables like the 2 decks, the statistics object, various strings, etc.,
    (iii) references to the created Model, View & Controller instances, and
    (iv) some debugging methods like a simple abortApplication(String S) which ends the program with a user message, or the pressAnyKey() which waits for the user to press a key.

    This class was created to solve a repeating problem I had while writing the code: where to define, create and store the various utility parts needed by the application, small and large. For example, the UI parts (graphics constants, and buttons, and windows, and dialogs, and fonts etc), the strings, which I wanted all in one place, (but neither the Model nor the View or the Controller were proper places in my mind), and the decks (which, in reality, exist in a drawer even when no-one is playing). Finally, what evolved was a smaller View class, containing the game related methods and through those, we get to this class which holds an abundance of the more elemental parts, a brick-a-brak, if you like. This way, the game code is more explicit, but this class is like a storehouse.
  • I have created a P7_GameView class to express the View part of the Model-View-Controller paradigm. Here I put the methods that reflect the results of user or program actions in the program windows. This class manipulates the UI fixed elements defined and created in the previous class. Its methods are called from the controller class primarily after some action (user or program based) has taken place.  The methods are:
    (i) the constructor. It takes the Game Model object as a parameter, as it needs the data in the objects contained there in order to display them to the player.
    (ii) updateGraphicalElements() shows the latest statistics, the deck and all the piles. It is called before & after a game to display the final state of the table to the player.
    (iii) showDeckAndPile() takes a deck and a column as parameters and shows them to the player. It is usually called after a user event on either the deck or some column.
    (iv) showTheWindows() send the open message to the Shell objects each time a game starts and after the creation of all the graphical elements.
    (v) then we have all the show methods: showCard(), showPile(), showStatistics(), and showAllPiles(). These do what their names suggest, i.e. they manipulate the SWT graphics objects to show the progressing of the game.
  • After the view class BUT before the controller class, I have defined four classes that extend Adapters (listeners) to handle the user events:
    (i) P7_DealButtonSelectionAdapter extends SelectionAdapter listens for user clicks on the deal button.
    Deal ButtonWhen this happens, the message processDealButton()is sent to the Controller. There, the Controller sends the deal() message to the Model object in order to update the Model data and the showDeck() and showPile() messages to the View object to update the UI.
    (ii) P7_PileButtonAction extends SelectionAdapter handles the input from buttons at the bottom of the window.
    Triplet ButtonsThere are 21 such buttons (3 rows x 7 columns) created by the P7_TripletButtonGrid class in the P7_FixedElements class. The user can either use these buttons to describe which triplet should be moved to the deck (B3=Bottom 3, T2B1=Top 2 Bottom 1, etc), or he can double-click on a (valid) card that describes (to the program) the triplet (more of that below).
    (iii) P7_DeckCardMouseAdapter extends MouseAdapter listens for double-clicks on the deck object and, as explained before sends the processDealButton() message to the Controller object.
    (iv) P7_PileCardMouseAdapter extends MouseAdapter listens for double-clicks on cards located in the columns. Specifically, if the user double-clicks on the first, second or third card from the bottom of any column, or on the top or second from the top card of any column, the processPileClick() message is sent to the Controller object along with the card (as a widget parameter) that was selected by the player. In that method, the Controller object (by asking the card, as will be explained in the Model description onwards) determines the column and row of the card, and sends messages to the Model object’s Pile objects to check if the triplet indicated by the card has a valid sum (again, we will have to get to the Model’s data objects for additional explanations). If not, nothing happens, otherwise, the cards are moved to the deck (messages send to the Model object) and the View is updated (again) with the showDeck() and showPile() messages. Please note this: as I was learning SWT by reading various code snippets, I started defining the event handlers and adapters as anonymous classes (parameters of the various add…Listener methods). As I learned more, I created the separate adapter classes listed above. However, some of the event handlers, I left where the widget was created in the P7_FixedElements class. Specifically, I did not put handlers in separate classes for the following controls:

    (i) the auto-play checkbox and the other related controls.
    (ii) the double-click in an empty area anywhere in the window that defaults to dealing a card.
    (iii) all those pop-up dialogs and confirmations. I will explain how all this works in the Model part further on.

    For now, all I can say is that the selection of these controls affects boolean and other variables in the Model with minimal other code so I thought that if I defined more classes it would obfuscate the program needlessly. However, for orthogonality, I am considering doing that in the future.
  • The Controller class is called P7_GameController and it has 9 methods and 1 constructor.
    The constructor has as parameters the P7_FixedElements object, the P7_GameView object and the P7_GameModel object because the controller needs to send messages to control both the Model of the game and its View. To do that it needs to know about the global elements, and its Model and View partners. I cannot use the FixedElements references to these entities yet, because when the FixedElements object is created, the MVC objects do not yet exist. The values at FixedElements construction time are null. Only after all the instances of MVC are created in the main() method (in the exact MVC order), I update the MVC references in FixedElements with an explicit message and from then on I can use them safely; there is only one instance of each.
    The Controller methods correspond to the processing of player events, or of automated play. Specifically, there is a process method for every major user event:
    (i) dealing a card event (button, double-click on deck or on the table -processDealButton()),
    (ii) double-clicking on a card to select a triplet (processPileClick()),
    (iii) exiting the program (window close, button - processUserExitRequest()).

    The play() method contains the main loop of the program. If autoplay is enabled, it evokes the runIt() method which deals and checks the column for triplets using the processPiles() method. If in manual mode, the loop evokes the SWT display sleep() method. The event listeners do all the work in manual mode.

    Finally, there are 3 other methods:
    (i) initialSetup() which shuffles the deck when the game runs for the first time and deals the first two rows of cards for every game afterwards;
    (ii) returnAllCardsToTheDeck() returns all cards to the deck at the end of a game;
    (iii) locateCard() returns the card that the user double-clicked on and is needed to identify the column/triplet.

    The methods were created as I “unfolded” the program logic.
  • Finally we get to the basic classes used by the Model:
    (The Card (P7_PlayingCard), the Pile/Column (P7_Pile/P7_PileIterator), the Deck (P7_PlayingDeck extends P7_Pile), the GameState (P7_GameState), and the GameModel (P7_GameModel). After those, there is one very simple class named PatienJava containing the main() method that just creates the basic game objects (FixedElements, Model, View, Controller) and then sends the play() message to the Controller continuously until it comes back with an exit flag raised.
    The P7_PlayingCard class is quite big. It evolved from being a simple Suit/Rank combo to a sophisticated object as I worked on the program. The main reason for this growth is that I store quite a bit of information for each card in the card itself. Most of this information if about the graphical appearance of the card: its SWT widget (a label), its coordinates (location) in the window, 2 image references (Face, Back), a reference to behavioral handlers which define the behavior of double-clicks depending on the location of the card (the deck or a column), flags to indicate if it is visible or if it is showing its face or its back to the player. I also use methods to set or reset these values. There are also two types of constructors: one to be used for creating “hollow” cards (for when the values are not yet known and will be created/assigned later), and one used to create “deep” cards when all the card properties are known.
    There is an assignment method:
    (assign(P7_PlayingCard Other))
    where a card’s values are replaced by the parameter’s values. However, this method will NOT do a deep copy, i.e. it will not create copies of heap created objects like the face & back images or the widgets. It will copy the references to these as per the Java paradigm. For this program, this is as designed, because it gains speed and there is no need to change the card’s face or back or widget type. Even if in future versions there is an option to use different decks (visual difference), the various graphic images will all be created when the game runs for the first time and, afterwards, the program will deal with references to these created objects. This approach also mirrors a real-life card’s attributes: a real card has a face & a back, both with specific graphics, has a location during play, is either visible or not, is “face-up” or not, can participate in various calculations & game procedures based on the location, or some rule or characteristic (this is implemented as the Adapter reference which the GameController and the GameModel can change/reset). The program deals with cards as if they were real, i.e. does not create and destroy them as virtual objects but rather passes their reference around just like we pass cards in a real game. In the end it returns all the cards from the columns to the deck. There are more methods to accommodate the needs of the game like switchCard() which swaps the values of the receiver with the playing card in the parameter.

    The next needed class is the column/pile class (P7_Pile). This class contains a P7_PlayingCard linked list with the column’s cards, a window location indicating its top/left (x,y) position, the column’s number (0..6), and the methods of the object.
    (i) The constructor takes the location and the index as parameters and creates an empty column.
    It can process these messages (methods):
    (ii) appendCard() adds a card at the bottom,
    (iii) getCardFromPosition(int Position) removes the card from the indicated position starting from the head down,
    (iv) referenceCard(int Pos) returns a reference to the cards but does NOT remove it from the column,
    (v) checkForT1B2Sum() or checkForT2B1Sum() or checkForB3Sum() all return a flag indicating if the triplet can be removed,
    (vi) outputPile() was used in debugging,
    (vii) get/setLocation(Point P) changes the values of the x,y coordinates of the column,
    (viii) lookUp() has 2 versions: one with a widget as a parameter and one with the card’s rank & suit as parameters. It returns a reference to the card that matches the parameters, and null if it cannot find it.
    (ix) getIndex() returns the column’s index (0..6)
    (x) swap(int I, int J) swaps the cards located at I & J respectively (from the top of the column).
    (xi) correctYValues() is a peculiar method; It is used to adjust the x,y location values in the cards of the column after a changes happens (i.e. the removal of a triplet). At some point during programming, I had to decide what would happen if a column was too long to fit in the game window. Instead of changing the window’s size, I chose to collapse some of the middle cards. Those cards are not part of any triplet so the player is not interested in them (other than the “visual” appearance of the “table.” I chose to create “mini-decks” when that happens, i.e. those cards all have the same location (the middle of the column) and they are face down, that is, the user cannot see their values but knows that the column has now “overflowed.” All these column will have that face-down card in their middle. Here is an screen shot:
    Main Game Window (Overflowed Column vs Just before)
    In this screen shot, you can see 2 columns that appear to be the same height, but only one of them has actually “overflowed” the space allocated to columns in the window; it is the one with the face-down card in the middle.

    I also created a P7_PileIterator class as a utility. It is like a regular int but, wraps around at the 0 and 6 boundaries. It does not “jump over” the empty columns though; the other classes check for that condition and act accordingly.

    A specialized case of the P7_Pile class is the P7_PlayingDeck.
    The constructor takes 2 arguments: the location (for the parent class), and a boolean that tells the class to construct a “hollow” deck to be used for copying. This is because in this constructor that the program creates the cards & reads their graphical images from the .gif files. If the deck will only copy another, there is no need to create new duplicate images in memory.
    It extends its parent class by adding the following methods:
    (i) the copy() method creates and returns a copy of the current deck,
    (ii) resetCardsLocation() visits all the cards in the deck and sets their x,y location to the deck’s x,y location. It also turns them face-down.
    (iii) shuffle(int Times) is the methods that shuffles the deck. It does that in two ways: the first is when its argument is less than 0. When that is the case, it will shuffle the deck for about 4 seconds before starting the game. If the argument is greater than 0, it will perform the indicated number of random card swaps.
    and it does
    (iv) P7_PlayingCard deal() returns the top card from the deck. It also changes the behavior of the card in the following ways: it instructs it to be open i.e. to show it’s “face,” and it also changes the cards double-click handler to handle column,instead of deck, logic.
    (v) boolean deckIsHealthy() checks a deck for completeness i.e. if all the cards for the game are in it. Returns true or false.
    (vi) static int lookupValue(P7_Rank R) is used when cards are being created to get the respective point values for the triplet sums.

    The GameModel class needs a small utility class called P7_GameState. In this class, which is used by the GameModel I store some needed variables for the present game:
    (i) StopGame is a boolean that indicates to the program if it should stop the game. It is used to exit loops.
    (ii) GameOver is a boolean that indicates to the program to exit completely. It gets this value from the various dialog boxes asking for player confirmation when the close window or the exit game buttons are pressed.
    (iii) NumberOfGamesToPlay holds the number of games to play in auto-play mode. –1 means play indefinitely. It is used when the player wants to see the success rate of the game.
    (iv) AutoPlayDelay gets its value from the slider to slow down execution during auto-play.
    (v) finally, CurrentPile is an interator that holds the current column in the game.

    At last, we got to the P7_GameModel class. In this class I create the columns and the game state objects. The 2 decks for the game are created in the P7_FixedElements class and they exist for the duration of the play.
    Its constructor takes a P7_GameModel class as an argument, because it will need access to the model in order to display the game’s progress.
    The rest of the class consists of methods for the manipulation of the cards & the statistics during the game:
    (i) numberOfCardsInDeck() is self explanatory; it returns the number of cards remaining in the deck.
    (ii) initializeGame() resets the flags that stop or end the game, returns any leftover cards from the columns back to the deck, and resets the current column to the first.
    (iii) subtractGameCounter() is used to decrease the game counter in autoplay and to update the value in the text field so that the user can see how many games are still to be played.
    (iv) deck() returns a reference to the current deck.
    (v) currentPile(),currentPileNo() returns a reference to or the number of the current column/pile.
    (vi) setStopGame(boolean B), getStopGame() set and get the boolean var that signifies that the game must or has stopped.
    (vii) areAllPilesEmpty() returns true if they are, and false if not.
    (viii) processSuccess() increments the total games played variable and the total successes variable in the statistics class object.
    (ix) processFailure() increments the total number of games played variable and the total failures variable in the statistics class object.
    (x) fromPile2Deck(P7_PlayingCard PC, boolean MoveCardsYLocation) changes the card in 2 ways: (a) makes it hidden again, and (b) sets its double-click handler to that of a deck card before it moves it from the column back to the bottom of the deck.
    (xi) the removeB3(…), removeT1B2(), & removeT2B1() methods move the corresponding triplet back to the deck.
    (xii) deal2Pile() deals the top card to the column indicated by GameState object. Returns true if a card was dealt or false if not, meaning the deck had no more cards to deal , so the game was lost.
    (xiii) the findNextValidPile() is used to locate the next column to deal to. It skips over any empty columns and returns true if a valid column was found, else it returns false: the game is won. The swapDecks() is used at the beginning of a game to switch to the offline shuffled deck.
  • We are now at the main class called PatienJava which only contains the public static void main(String[] args) method where execution of the program begins, the args are not used. Here 2 things happen: (a) the instances of the P7_FixedElements and of the P7_GameModel, P7_GameView, and P7_GameController classes are created, and (b) we loop while the player wants to keep playing games.
    Each loop is a game returning a continuation yes/no boolean.
    The last dispose method returns the resources to the OS before exiting the program.
  • In the end, there is one class that I copied (as is seen in the code) from the net to implement the evocation of the browser to show this blog.
  • And that’s it!

Nestor Louizidis

Create your badge

2 comments:

  1. Great write-up! Lots of good information. Thanks and have a nice day. Bob

    ReplyDelete