/*

Copyright (C) 2008 Paul Swanson.

Java SDK 1.6 - Get with it and upgrade.

*/

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
import javax.swing.*;

// Constants for communication between classes

enum SnapVocab {
    DUPLICATE_CARDS, YOUR_TURN, GAME_OVER
}

// A very basic card class

class PlayingCard {

    final static protected String[] frenchFaceValues = {"Ace","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Jack","Queen","King"};
    final static protected String[] frenchSuitValues = {"Spades","Clubs","Hearts","Diamonds"};

    final protected int value; // Protected distinction differs in Java 1.6 to Lecture notes
    final protected int suit;
        
    PlayingCard(int value) {
        this.value = value;
        suit = 0;
    }
    
    PlayingCard(int value, int suit) {
        this.value = value;
        this.suit = suit;
    }
               
    public int getValue() { return value; }
    public int getSuit() { return suit; }
           
    // String version of getValue routine
    public String getStringValue() { return frenchFaceValues[value]; }

    // String version of getSuit routine
    public String getStringSuit() { return frenchSuitValues[suit]; }
    
    // String version of full card name
    public String getName() { return getStringValue() + " of " + getStringSuit(); }
}

// Dealer class prepares and deals cards

class Dealer {

    protected int numberOfPlayers;      // How many Players (hands) being dealt
    protected int deckSize;             // How many cards in the deck
    protected int handSize = 0;         // How many cards in each hand
    protected int excessCards = 0;      // Sometimes there will be spare cards for dealing
    protected LinkedList<PlayingCard> deckOfCards; // ArrayLists can be shuffled easily

    // Initialise and build a deck of cards to use
    Dealer(int numberOfPlayers) {
        this.numberOfPlayers = numberOfPlayers;
        this.deckSize = 52;
        calcPortions();
        buildDeck();        
    }

    // Calculate the portion of cards for each Player
    protected void calcPortions(){
        if(numberOfPlayers > 0) { 
            // Number of cards for each player less spares, spare are dealt on a first come first serve basis
            handSize = (int)((deckSize - excessCards) / numberOfPlayers);
            excessCards = deckSize % numberOfPlayers;
        }    
    }
    
    // This critical method creates and populates deckOfCards
    protected void buildDeck() {
    
        int suitCount;
        int valueCount;
        ArrayList<PlayingCard> newDeck = new ArrayList<PlayingCard>();        
        
        // Populate deckOfCards with PlayingCards
        for(suitCount = 0; suitCount < 4; suitCount++) {
            for(valueCount = 0; valueCount < 13; valueCount++) {
                newDeck.add(new PlayingCard(valueCount,suitCount));
            }
        }      
        Collections.shuffle(newDeck); // Convenient to shuffle whilst already an ArrayList
        deckOfCards = new LinkedList<PlayingCard>();
        deckOfCards.addAll(newDeck);
    }
    
    // Shuffles the deck
    public void shuffleDeck() {
        ArrayList<PlayingCard> workingCards = new ArrayList<PlayingCard>(); // ArrayList has shuffle function, LinkedList doesn't

        say("Shuffling deck");
        workingCards.addAll(deckOfCards);   // This abstraction causes a compiler warning in Java 5.0+ but seems OK to me
        Collections.shuffle(workingCards);
        deckOfCards.clear();
        deckOfCards.addAll(workingCards);   // This abstraction causes a compiler warning in Java 5.0+ but seems OK to me
    }    

    // Deal out a single portion of the deck
    public LinkedList<PlayingCard> dealCards() {

        LinkedList<PlayingCard> dealtCards = new LinkedList<PlayingCard>(); // LinkedLists are more suitable for this next stage

        if ((numberOfPlayers > 0) && (deckOfCards.size()>0)) { // This is a very fair weather test! Needs bolstering
            // Deal enough cards for 1 player from deckOfCards
            for(int i=0; i < handSize; i++){    
                try {
                    dealtCards.add(deckOfCards.removeFirst());
                }
                catch(NoSuchElementException e){
                    System.out.println("PlayingCardDealer.dealcards(): Ran out of cards whilst dealing (this isn't supposed to happen)!");
                }
            }
            // Deal the player one excess card if there are some. First come, first serve
            if(excessCards > 0){
                try {
                    dealtCards.add(deckOfCards.removeFirst());
                }
                catch(NoSuchElementException e){
                    System.out.println("PlayingCardDealer.dealcards(): Ran out of cards whilst dealing (this isn't supposed to happen)!");
                }
                excessCards--;
            }
            return dealtCards;
        } else
            return new LinkedList<PlayingCard>(); // A null list means no cards
        
    }
    
    public void say(String message){
        System.out.println("Dealer: " + message);
    }
        
    // Output the deck to console. Mostly for testing purposes at this stage.
    public void showDeck() {

       for (int i=0; i < deckSize; i++) {
            System.out.println(deckOfCards.get(i).getStringValue() + " of " + deckOfCards.get(i).getStringSuit());
        }
    }
    
    public int getDeckSize(){
        return deckSize;
    }
    
    // Returns number of players being dealt
    public int getNumberOfPlayers() { return numberOfPlayers; }
    
}

// This class represents one computer player in the game
// This class could be retrospectively abstracted to enable human players to interact with the computer players

class Player implements Runnable {

    protected Snap game;                            // A reference to the active object
    protected String name;                          // The players name
    protected int playerNumber;                     // The players unique number, corresponds to order
    protected LinkedList<PlayingCard> playersHand;  // The players cards at any given time
    final protected int agilityVariance = 200;      // How much variance should there be in player agility
    protected int minReactionTime = 50;             // The minimum reaction delay, should stay constant for all players
    protected int agility = 100;                    // Default value. This can be used to give personality to players
    protected boolean myTurn = true, threadSuspended = false, iQuit = false, snapTime = false; // Events flags
    
    protected Button btnSuspendResume;

    Panel pnlMain;
    Label lblPlayerName, lblCardCount, lblStatus;
    
    // Player constructor
    Player(String name, Snap game) {
    
        this.name = name;
        this.game = game;
        
        // Don't use say() before status label is created
        System.out.println("Player \"" + name + "\" has joined the game."); 

        // Get cards
        playersHand  = new LinkedList<PlayingCard>();
        playersHand.addAll(game.getHand());

        // Set individual player agility (speed of snapping)
        agility = (int)(Math.random() * agilityVariance);

        // Prepare the user interface elements
        pnlMain = new Panel();
        pnlMain.setLayout(new GridLayout(4,1));
        pnlMain.add(lblPlayerName = new Label(name,Label.CENTER));
        pnlMain.add(lblCardCount = new Label("",Label.CENTER));
        pnlMain.add(lblStatus = new Label("Ready",Label.CENTER));
        pnlMain.add(btnSuspendResume = new Button("Suspend"));
        btnSuspendResume.addActionListener(new buttonAdapter (0,this));
        
        lblPlayerName.setFont(new Font(Font.SANS_SERIF,Font.BOLD,16));
               
        showCardCount();
        
        say("Collected " + playersHand.size() + " cards from the dealer");
    }
    
    // Provide feedback to System.out
    protected void say(String message) {
        System.out.println("Player \"" + name + "\" (" + numberOfCards() + "): " + message);
        lblStatus.setText(message);
    }
    
    // Take a card from the playersHand and put on the game pile
    private void playCard(){
        PlayingCard preview = playersHand.peek();
        say(preview.getName());
        game.putCard(playersHand.removeFirst());
        showCardCount();
    }

    // Deal with asynchronous events in the game
    public synchronized void playGame() {
        while (!iQuit) {
            if(snapTime && !threadSuspended) {  // Yes, there are two cards available for snapping and thread is not suspended
                try{
                    Thread.sleep((int)(minReactionTime + (Math.random() * agility))); // Simulate reaction response time
                } catch (InterruptedException e) {}
                LinkedList<PlayingCard> c = game.snapCards(); // Snap attempt
                if (c.size() > 0) {     // If PlayCards are returned the snap attempted was successful
                    say("Snapped " + ((Integer)c.size()).toString());
                    getCards(c);
                    showCardCount();
                    snapTime = false;
                    myTurn = true;      // Player won the snap so starts next
                    if(playersHand.size()==game.snapDealer.getDeckSize()){
                        say("I win!");
                        game.announce(name + " wins!");
                        myTurn = false;
                        iQuit = true;
                        game.gameOver = true;
                    }
                    try{
                        Thread.sleep(500); // Pause to display the achievement
                    } catch (InterruptedException e){}
                } else {
                    snapTime = false;
                    myTurn = false; // Skip next go so that winning snapper can lead
                }
            }
            if(myTurn) { // Playing in unsynchronised mode
                yourTurn();
            }            
            try {
                wait();
            } catch (InterruptedException e) {}
        }              
    }
    
    // Player places a card on the table in a synchronised manner (ie. in turn)
    public synchronized void yourTurn() {
        if ((playersHand.size()>0)){    // If a player has cards they should take a turn
            if(threadSuspended) {       // A player has decided to take a break, this may or may not hold up game play accordingly
                try {
                    wait();
                } catch (InterruptedException e) {}
            }
            playCard();
        } else if (playersHand.size()==0) say("No cards");
        myTurn = false;  // TODO This was set to true...?
    }
    
    // Manage requests to Suspend and Resume the Player
    public synchronized void suspendResume() {
        threadSuspended = !threadSuspended;
        if (!threadSuspended){
            say("Player Resumed");
            btnSuspendResume.setLabel("Suspend");
            notifyAll();            
        } else {
            say("Player Suspended");
            btnSuspendResume.setLabel("Resume");
        }
    }
        
    // Start the Player thread
    public void run() {
        say("Ready and waiting");
        playGame();
    }
    
    // Snap class uses this method to communicate with Player
    public void tell(SnapVocab message) {
        switch(message){
            case DUPLICATE_CARDS: snapTime = true; break;
            case YOUR_TURN: myTurn = true; break;
            case GAME_OVER: iQuit = true; break;        
        }
    }
    
    // Add cards to playersHand
    public void getCards(LinkedList<PlayingCard> cards){
        playersHand.addAll(cards); 
    }
    
    // Show cards in playersHand
    public LinkedList<PlayingCard> showCards(){
        return playersHand;
    }
    
    // How many cards in the players hand
    public int numberOfCards(){ return playersHand.size(); }    
    
    // Update the players card count
    private void showCardCount() {
        lblCardCount.setText("(Cards: " + Integer.toString(numberOfCards()) + ")");
    }
    
    // Update the players status label
    public void setStatus(String message) {
        lblStatus.setText(message);
    }
    
    // Custom ActionListener class to parse input from control buttons   
    class buttonAdapter implements ActionListener {
    
        private int id;
        private Player snapPlayer;
        
        // Constructor method
        buttonAdapter (int buttonID, Player snapPlayer) {
            id = buttonID;
            this.snapPlayer = snapPlayer;
        }
        
        // Map button clicks to certain corresponding actions
        public synchronized void actionPerformed(ActionEvent e) {
            switch (id) {
                case 0:     //Suspend / Resume the game
                    say("Click");
                    suspendResume();
                    break;
            }
        }
    }  
}

// This class is responsible for the running of, or mediating of, the game

class Snap implements Runnable {

    Dealer snapDealer;      // Dealer generate the deck and deals
    Player[] snapPlayers;   // Store Players
    Thread[] playerThreads; // Store Player's threads
    Thread gameMonitor;     // Connection to the game monitor
        
    Panel pnlMain = new Panel();
    Panel pnlPlayers = new Panel();
    Panel pnlCardPile = new Panel();
    Panel pnlControls = new Panel();
    
    Button btnSuspendResume, btnSpeedUp, btnSlowDown, btnStopRestart;
    Label lblTopCard, lblSecondCard;
    Checkbox chbSynchronized;
   
    private int numberOfPlayers;                // How many players in this game
    private LinkedList<PlayingCard> cardPile = new LinkedList<PlayingCard>(); // The card pile
    private boolean duplicateCards = false;     // Are the top two cards on the pile of duplicate value
    boolean gameOver = false;                   // Game over flag
    private int turnDelay = 700;                // Default pause between turns, used to regulate overall speed
    private int snapDelay = 50;                 // Enables winning snapper to start next hand
    private boolean threadSuspended = false;    // Suspend / resume flag
    private boolean synchronizedMode = true;
        
    // Constructor prepares a new game and generates the players and their threads
    Snap(int numberOfPlayers) {
        say("Loading a new game of Snap with " + numberOfPlayers + " players.");
        if(numberOfPlayers > 0){
            this.numberOfPlayers = numberOfPlayers;

            snapDealer = new Dealer(numberOfPlayers);
            snapPlayers = new Player[numberOfPlayers];
            playerThreads = new Thread[numberOfPlayers];

            // Add the display panels
            pnlMain.setLayout(new GridLayout(3,1));
            pnlPlayers.setLayout(new GridLayout(2,2,5,10));
            // pnlPlayers.setLayout(new GridBagLayout());
            // pnlCardPile.setLayout(new BoxLayout(pnlCardPile,BoxLayout.PAGE_AXIS));
            pnlCardPile.setLayout(new GridBagLayout());
            pnlControls.setLayout(new FlowLayout(FlowLayout.CENTER));
            
            GridBagConstraints c = new GridBagConstraints();
            
            // Keeping players and threads in arrays means a variable number of players can participate
            for(int i = 0; i < numberOfPlayers; i++){
                snapPlayers[i] = new Player("Player " + i, this);
                playerThreads[i] = new Thread(snapPlayers[i]);
                pnlPlayers.add(snapPlayers[i].pnlMain);
            }

            // Add labels to the card pile panel
            c.weightx = 1.0;
            c.fill = GridBagConstraints.HORIZONTAL;
            c.gridx = 0;
            c.gridy = 0;
            //c.insets = new Insets(0,5,0,5);
            pnlCardPile.add(lblTopCard = new Label("Top Card",Label.CENTER),c);

            c.weightx = 1.0;
            c.fill = GridBagConstraints.HORIZONTAL;
            c.gridx = 0;
            c.gridy = 1;
            //c.insets = new Insets(0,5,0,5);
            pnlCardPile.add(lblSecondCard = new Label("Second Card",Label.CENTER),c);
            
            lblTopCard.setFont(new Font(Font.SANS_SERIF,Font.BOLD,16));
            
            // Add Suspend / Resume, Speed Up, Slow Down, Quit buttons to the control panel
            pnlControls.add(btnSuspendResume = new Button("Suspend"));
            btnSuspendResume.addActionListener(new buttonAdapter (0,this));            
            pnlControls.add(btnSpeedUp = new Button("Speed up"));
            btnSpeedUp.addActionListener(new buttonAdapter (1,this));
            pnlControls.add(btnSlowDown = new Button("Slow down"));
            btnSlowDown.addActionListener(new buttonAdapter (2,this));            
            pnlControls.add(btnStopRestart = new Button("Stop"));
            btnStopRestart.addActionListener(new buttonAdapter (3,this));
            pnlControls.add(chbSynchronized = new Checkbox("Synchronized", true));
            chbSynchronized.addItemListener(new buttonAdapter (4,this));
            
            c.weightx = 1.0;
            c.fill = GridBagConstraints.HORIZONTAL;
            c.gridx = 0;
            c.gridy = 2;
            c.insets = new Insets(50,0,0,0);
            pnlCardPile.add(pnlControls,c);
            
            pnlMain.add(pnlPlayers);
            pnlMain.add(pnlCardPile);
            // pnlMain.add(pnlControls);
            
        } else say("What, no players?!");
        
    }

    // Sleep for a little while to regulate game speed
    private void doTurnDelay(){
        try {        
            Thread.sleep(turnDelay);
        } catch(InterruptedException e) {}        
    }
    
    // Wake up this thread nicely
    public synchronized void wakeUp(){
        notify();
    }
    
    // Provide feedback to System.out
    protected void say(String message) {
        System.out.println("Snap: " + message);
    }
        
    // Dealer provides Player with opening hand of cards
    public synchronized LinkedList<PlayingCard> getHand() {
        return(snapDealer.dealCards());
    }
    
    // Place a card face up on the pile
    public synchronized void putCard(PlayingCard topCard) {
        PlayingCard secondCard;
        duplicateCards = false;     // Failsafe / reset the duplicates flag
        if(cardPile.size()>0) {     // Don't attempt to peek at cardPile if it's empty
            secondCard = cardPile.peek();
            if(topCard.getValue() == secondCard.getValue()) { // If there's one or more cards in the pile check for duplicate
                announce(topCard.getName(),secondCard.getName());
                cardPile.push(topCard);
                duplicateCards = true;
                for(int i=0; i < numberOfPlayers; i++){     // Notify all players that it's snapping time
                    snapPlayers[i].tell(SnapVocab.DUPLICATE_CARDS); // Tell the Player that there is a duplicate to snap
                    if(playerThreads[i].isAlive()) { 
                        playerThreads[i].interrupt();       // Wake up the thread
                    }
                }
                try{
                    Thread.sleep(snapDelay);    // Delay ensures winning snapper starts next
                } catch(InterruptedException e){}
            } else {    // Just place the first card on the pile
                lblTopCard.setText(topCard.getName());
                lblSecondCard.setText(secondCard.getName());
                cardPile.push(topCard);
            }
        } else {    // Just place the first card on the pile
            lblTopCard.setText(topCard.getName());
            cardPile.push(topCard);
        }
        // Check for a draw
        if(cardPile.size()==snapDealer.getDeckSize()){
            say("No winner. It's a draw.");
            announce("No winner. It's a draw.");
            gameOver = true;
        }
    }

    // Try and snap the pile
    public synchronized LinkedList<PlayingCard> snapCards() {
        // If a successful snap then give cardPile to player
        if (duplicateCards) {            
            duplicateCards = false;
            LinkedList<PlayingCard> passTheCards = new LinkedList<PlayingCard>();
            passTheCards.addAll(cardPile);
            cardPile.clear();
            announce("Snap!");
            return passTheCards;
        } else return new LinkedList<PlayingCard>(); // Otherwise return nothing

    }
    
    // Manage requests to Suspend and Resume
    public synchronized void suspendResume() {
        threadSuspended = !threadSuspended; // Toggle threadSuspended flag
        if (!threadSuspended){
            say("Game resumed");
            btnSuspendResume.setLabel("Suspend");
            notify();            
        } else {
            say("Game suspended");
            btnSuspendResume.setLabel("Resume");
        }
    }
    
    // Toggle synchronizedMode
    public void synchronizedMode() {
        synchronizedMode = !synchronizedMode;
        say("synchronizedMode set to " + synchronizedMode);
    }
    
    // Adjust turn delay, controls speed of the game
    public void setTurnDelay(int delay) {
        turnDelay = delay; // Note: turnDelay should always be greater than the maximum Player reaction time
    }
    
    // Returns current turn delay
    public int getTurnDelay(){
        return turnDelay;
    }    
    
    // Make a prominent announcement
    public void announce(String message){
        lblTopCard.setText(message);
        lblSecondCard.setText("");
    }
    public void announce(String message, String subtitle){
        lblTopCard.setText(message);
        lblSecondCard.setText(subtitle);
    }
    
    // Check if the pile is empty
    public boolean pileEmpty() {
        return cardPile.isEmpty();
    }
    
    public void run() {
        
        say("Game started.");        
      
        // Execute the player threads
        for(int i=0; i < numberOfPlayers; i++){
            playerThreads[i].start();
        }
        
        // Commence gameplay
        while(!gameOver){
            // Cycle through all the players in turn
            for(int i = 0; i < numberOfPlayers; i++) {
                gameLoop:
                if(gameOver) break;     // Don't wait for a full cycle to quit if GameOver
                else {
                    try {        
                        synchronized(this) {
                            while (threadSuspended) // If Suspend clicked, wait for user to Resume
                                wait();
                                if(gameOver) break gameLoop; // Test if Stop was clicked whilst suspended
                        }
                    } catch(InterruptedException e) {}
                    if(playerThreads[i].isAlive()) {            // Don't poll a dead thread, you just never know
                        if(snapPlayers[i].numberOfCards()>0) {  // Skip players with no cards
                            if(synchronizedMode){               // Normal synchronised gameplay
                                snapPlayers[i].yourTurn();      // Necessary to call a method for synchronised gameplay
                                doTurnDelay();                  // Pause between player turns to regulate speed of gameplay
                                snapPlayers[i].setStatus("");   // Tidy up the player status label
                            } else {                            // Unsynchronised gameplay, unreliable, particularly as game gets faster
                                snapPlayers[i].tell(SnapVocab.YOUR_TURN); // Tell the player it's their turn
                                playerThreads[i].interrupt();   // Wake the player's thread up
                                doTurnDelay();
                                snapPlayers[i].setStatus("");                                
                            }
                        }                      
                    }
                }
            }
        }
        btnStopRestart.setLabel("Start"); // Change button text in case of win or draw
        say("Game over.");
    }
    
    // Custom ActionListener class to parse input from control buttons   
    class buttonAdapter implements ActionListener, ItemListener {
    
        private int id;
        private Snap snapGame;
        
        // Constructor method
        buttonAdapter (int buttonID, Snap snapGame) {
            id = buttonID;
            this.snapGame = snapGame;
        }
        
        // Map button clicks to certain corresponding actions
        public synchronized void actionPerformed(ActionEvent e) {
            switch (id) {
                case 0: //Suspend / Resume the game
                        suspendResume();
                        break;
                        
                case 1: // Speed up the pace of the game
                        if(getTurnDelay()>200) {
                            setTurnDelay(getTurnDelay()-100);
                            say("Speeding up");
                        } else say("Can't go any faster");                        
                        break;
                        
                case 2: // Slow down the pace of the game
                        if(getTurnDelay()< 2100) {
                            setTurnDelay(getTurnDelay()+100);
                            say("Slowing down");
                        } else say("Can't go any slower");                        
                        break;
                        
                case 3: // Manage Stop / Restart game functionality
                        if(!gameOver) {
                            // End the current game
                            btnStopRestart.setLabel("Start");
                            gameOver = true;
                            threadSuspended = false; // Clear the threadSuspended flag, in case of quit during suspend
                            wakeUp();   // Wake thread up gentley if suspended
                        } else {
                            // Call on the GameMonitor to start a new game
                            btnStopRestart.setLabel("Stop");
                            gameOver = false;
                            gameMonitor.interrupt(); // Wake up the GameMonitor for a new game
                        }
                        break;
            }
        }
        
        public synchronized void itemStateChanged(ItemEvent e) {
            switch (id) {
                case 4: // Toggle Synchronization mode
                        synchronizedMode();
                        break;
            }
        }
    }
}

// GameMonitor runs parallel to the Snap game and enables the user to restart the game
class GameMonitor implements Runnable {

    Snap snapGame;
    Thread snapThread;
    SnapApplet hostApplet;
    
    // Constructor connects game monitor to the appropriate applet, game and thread 
    GameMonitor(SnapApplet hostApplet, Snap snapGame, Thread snapThread){
        this.hostApplet = hostApplet;
        this.snapGame = snapGame;
        this.snapThread = snapThread;
    }
    
    // Monitor the Snap game and enable the user to restart it
    public void run(){
        while(true){

            System.out.println("Game monitor: Joining");

            // Connect to the current snapThread and wait till it finishes (ie. game over)
            try{
                snapThread.join();
            }catch(InterruptedException e){}

            // Wait for the user click "Restart" before starting a new game
            try {
                synchronized(this) {
                        wait();
                }
            } catch(InterruptedException e) {}

            // Start a new Snap game
            snapGame = new Snap(hostApplet.numberOfPlayers);

            // Connect the new game to the GameMonitor
            snapGame.gameMonitor = hostApplet.snapMonitor;

            // Clean up the applet and add draw the new interface
            hostApplet.removeAll();
            hostApplet.add(snapGame.pnlMain);
            hostApplet.validate();

            // Start the Snap game
            snapThread = new Thread(snapGame);
            snapThread.start();
        }
    }
}

// The main Applet class

public class SnapApplet extends Applet {

    StringBuffer buffer;
    Snap mySnapGame;
    GameMonitor mySnapMonitor;    
    Thread snapThread;
    Thread snapMonitor;
    final static int numberOfPlayers = 4;
        
    public void init() {    
        buffer = new StringBuffer();
    }
    
    public void start() {
        System.out.println("Starting applet...");

        // Load a new Snap game with 4 computer players
        mySnapGame = new Snap(numberOfPlayers);
        
        // Hand the Snap game over to a thread to be executed
        snapThread = new Thread(mySnapGame);
        
        // Prepare the applet's user interface
        add(mySnapGame.pnlMain);
        validate();
        
        // Load the GameMonitor and execute it as a thread
        mySnapMonitor = new GameMonitor(this,mySnapGame,snapThread);
        snapMonitor = new Thread(mySnapMonitor);
        mySnapGame.gameMonitor = snapMonitor;
        
        // Start the first game of Snap 
        snapThread.start();
        
        // Start monitoring the first game of Snap
        snapMonitor.start();
    }    
    public void stop() {
        System.out.println("Stopping applet...");
    }
    public void destroy() {
        System.out.println("Preparing for unloading applet...");
    }
    void addItem(String newWord) {
        System.out.println(newWord);
        buffer.append(newWord);
        repaint();
    }
    public void paint(Graphics g) {
        g.drawString(buffer.toString(), 5, 15);
    } 

}
