Package ozone.owf.sample.applet

Source Code of ozone.owf.sample.applet.ChessBoard

package ozone.owf.sample.applet;
//--------------------------------------------------------------------------------------
// ChessBoard.java
//
// This is the main source file for the MyChessViewer applet which plays over chess
// games stored in the Portable Game Notation (PGN) standard.
//
// Author - Michael Keating
//--------------------------------------------------------------------------------------

import java.applet.Applet;
import java.awt.Button;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.MenuItem;
import java.awt.Point;
import java.awt.PopupMenu;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.ZipInputStream;

import javax.swing.ImageIcon;

import sun.plugin.javascript.JSObject;

public class ChessBoard extends Applet implements MouseListener, MouseMotionListener, Runnable, ActionListener, ItemListener, WindowListener
{
  private static final long serialVersionUID = -1643046262117578746L;

  final String NAGStrings[] = "null annotation",
                  "Good move (!)",
                  "Poor move (?)",
                  "Excellent or brilliant move (!!)",
                  "Very poor move/blunder (??)",
                  "Speculative move (!?)",
                  "Questionable move (?!)",
                  "Forced move (all others lose quickly)",
                  "Singular move (no reasonable alternatives)",
                  "Worst move",
                  "Drawish position",
                  "Equal chances, quiet position (=)",
                  "Equal chances, active position",
                  "Unclear position",
                  "White has a slight advantage (+=)",
                  "Black has a slight advantage (=+)",
                  "White has a moderate advantage (+/-)",
                  "Black has a moderate advantage (-/+)",
                  "White has a decisive advantage (+-)",
                  "Black has a decisive advantage (-+)",
                  "White has a crushing advantage (+--)",
                  "Black has a crushing advantage (--+)",
                  "White is in zugzwang",
                  "Black is in zugzwang",
                  "White has a slight space advantage",
                  "Black has a slight space advantage",
                  "White has a moderate space advantage",
                  "Black has a moderate space advantage",
                  "White has a decisive space advantage",
                  "Black has a decisive space advantage",
                  "White has a slight time (development) advantage",
                  "Black has a slight time (development) advantage",
                  "White has a moderate time (development) advantage",
                  "Black has a moderate time (development) advantage",
                  "White has a decisive time (development) advantage",
                  "Black has a decisive time (development) advantage",
                  "White has the initiative",
                  "Black has the initiative",
                  "White has a lasting initiative",
                  "Black has a lasting initiative",
                  "White has the attack",
                  "Black has the attack",
                  "White has insufficient compensation for material deficit",
                  "Black has insufficient compensation for material deficit",
                  "White has sufficient compensation for material deficit",
                  "Black has sufficient compensation for material deficit",
                  "White has more than adequate compensation for material deficit",
                  "Black has more than adequate compensation for material deficit",
                  "White has a slight center control advantage",
                  "Black has a slight center control advantage",
                  "White has a moderate center control advantage",
                  "Black has a moderate center control advantage",
                  "White has a decisive center control advantage",
                  "Black has a decisive center control advantage",
                  "White has a slight kingside control advantage",
                  "Black has a slight kingside control advantage",
                  "White has a moderate kingside control advantage",
                  "Black has a moderate kingside control advantage",
                  "White has a decisive kingside control advantage",
                  "Black has a decisive kingside control advantage",
                  "White has a slight queenside control advantage",
                  "Black has a slight queenside control advantage",
                  "White has a moderate queenside control advantage",
                  "Black has a moderate queenside control advantage",
                  "White has a decisive queenside control advantage",
                  "Black has a decisive queenside control advantage",
                  "White has a vulnerable first rank",
                  "Black has a vulnerable first rank",
                  "White has a well-protected first rank",
                  "Black has a well-protected first rank",
                  "White has a poorly protected king",
                  "Black has a poorly protected king",
                  "White has a well-protected king",
                  "Black has a well-protected king",
                  "White has a poorly placed king",
                  "Black has a poorly placed king",
                  "White has a well-placed king",
                  "Black has a well-placed king",
                  "White has a very weak pawn structure",
                  "Black has a very weak pawn structure",
                  "White has a moderately weak pawn structure",
                  "Black has a moderately weak pawn structure",
                  "White has a moderately strong pawn structure",
                  "Black has a moderately strong pawn structure",
                  "White has a very strong pawn structure",
                  "Black has a very strong pawn structure",
                  "White has poor knight placement",
                  "Black has poor knight placement",
                  "White has good knight placement",
                  "Black has good knight placement",
                  "White has poor bishop placement",
                  "Black has poor bishop placement",
                  "White has good bishop placement",
                  "Black has good bishop placement",
                  "White has poor rook placement",
                  "Black has poor rook placement",
                  "White has good rook placement",
                  "Black has good rook placement",
                  "White has poor queen placement",
                  "Black has poor queen placement",
                  "White has good queen placement",
                  "Black has good queen placement",
                  "White has poor piece coordination",
                  "Black has poor piece coordination",
                  "White has good piece coordination",
                  "Black has good piece coordination",
                  "White has played the opening very poorly",
                  "Black has played the opening very poorly",
                  "White has played the opening poorly",
                  "Black has played the opening poorly",
                  "White has played the opening well",
                  "Black has played the opening well",
                  "White has played the opening very well",
                  "Black has played the opening very well",
                  "White has played the middlegame very poorly",
                  "Black has played the middlegame very poorly",
                  "White has played the middlegame poorly",
                  "Black has played the middlegame poorly",
                  "White has played the middlegame well",
                  "Black has played the middlegame well",
                  "White has played the middlegame very well",
                  "Black has played the middlegame very well",
                  "White has played the ending very poorly",
                  "Black has played the ending very poorly",
                  "White has played the ending poorly",
                  "Black has played the ending poorly",
                  "White has played the ending well",
                  "Black has played the ending well",
                  "White has played the ending very well",
                  "Black has played the ending very well",
                  "White has slight counterplay",
                  "Black has slight counterplay",
                  "White has moderate counterplay",
                  "Black has moderate counterplay",
                  "White has decisive counterplay",
                  "Black has decisive counterplay",
                  "White has moderate time control pressure",
                  "Black has moderate time control pressure",
                  "White has severe time control pressure",
                  "Black has severe time control pressure",
                  "With the idea",
                  "Aimed against",
                  "Better is this",
                  "Worse is this",
                  "Equivalent is this",
                  "Editor's remark",
                  "Novelty",
                  "Weak point",
                  "Endgame",
                  "Line",
                  "Diagonal",
                  "White has pair of bishops",
                  "Black has pair of bishops",
                  "Bishops of opposite color",
                  "Bishops of same color",
                  "White has united pawns",
                  "Black has united pawns",
                  "White has separated pawns",
                  "Black has separated pawns",
                  "White has doubled pawns",
                  "Black has doubled pawns",
                  "White has passed pawn",
                  "black has passed pawn",
                  "White has adv. in no. pawns",
                  "Black has adv. in no. pawns"};

    final int squareWidth         = 45;
    final int squareHeight        = 45;
    final int leftMargin          = squareWidth  / 2 + 3;
    final int rightMargin         = squareWidth  / 2 + 3;
    final int topMargin           = squareHeight / 2 + 3;
    final int bottomMargin        = squareHeight / 2 + 3;
    final int boardWidth          = (squareWidth  * 8) + (leftMargin * 2);
    final int boardHeight         = (squareHeight * 8) + (topMargin  * 2);
    final int rightSpaceLeft      = boardWidth;
    final int rightSpaceTop       = 0;
    final int rightSpaceWidth     = 220;
    final int rightSpaceHeight    = boardHeight;
    final int tagBoxHeight        = 25;
    final int moveLines           = 20;
    final int scoreSheetLeft      = rightSpaceLeft   + 5;
    final int scoreSheetTop       = rightSpaceTop    + 5;
    final int scoreSheetWidth     = rightSpaceWidth  - 10;
    final int scoreSheetHeight    = rightSpaceHeight - 10;
    final int scoreSheetRight     = scoreSheetLeft   + scoreSheetWidth;
    final int scoreSheetMovesTop  = scoreSheetTop  + (tagBoxHeight * 4);
    final int scoreSheetMiddle    = scoreSheetLeft + (scoreSheetWidth / 2);
    final int boxForMoveNumsWidth = 20;
    final int moveBoxHeight       = (scoreSheetHeight - (tagBoxHeight  * 4)) / moveLines;
    final int moveBoxWidth        = ((scoreSheetWidth / 2) - boxForMoveNumsWidth) / 2;
    final int whitesLeftColLeft   = scoreSheetLeft     + boxForMoveNumsWidth;
    final int whitesRightColLeft  = scoreSheetMiddle   + boxForMoveNumsWidth;
    final int blacksLeftColLeft   = whitesLeftColLeft  + moveBoxWidth;
    final int blacksRightColLeft  = whitesRightColLeft + moveBoxWidth;
    final int bottomSpaceLeft     = 0;
    final int bottomSpaceTop      = topMargin + (squareHeight * 8) + bottomMargin;
    final int bottomSpaceWidth    = boardWidth + rightSpaceWidth;
    final int bottomSpaceHeight   = 150;

    int      pieceOffsetX;
    int      pieceOffsetY;
    int      squarePressed;
    int      animatedPieceX, animatedPieceY;
    int      animatedPieceOldX, animatedPieceOldY;
    int      draggedPieceX, draggedPieceY;
    int      draggedPieceToMouseX, draggedPieceToMouseY;
    int      animatedSourceSquare;
    int      animatedDestSquare;
    int      iKilobytesDownloaded;
    int      intLastRAVMoveIndex;
    int      intRefreshInterval;
    char     animatedSquare;
    char     animatedPiece;
    char     draggedPiece;
    char     animationPosition[];
    Color    squareLightColor, squareDarkColor, backgroundColor;
    boolean  whiteFromBottom;
    boolean  isDraggingOn;
    boolean  isReadingPGN;
    boolean  isAnimation;
    boolean  bWhiteToMove;
    boolean  bInitError;
    boolean  showPGNGameList;
    boolean  bIsDownloading;
    boolean  bAnimateMoves;
    boolean  bWaitForFrameToPaint;
    boolean  bPaintBoardOnly;
    boolean  bPuzzleMode;
    boolean  bShowAnswer;
    boolean  bShowSideOnMove;
    boolean  bWindowClosed;
    boolean  bWidget;
    Image    wPawnImage, wKnightImage, wBishopImage, wRookImage, wQueenImage, wKingImage,
             bPawnImage, bKnightImage, bBishopImage, bRookImage, bQueenImage, bKingImage;
    Image    offImage;
    Vector   pgnGames;
    Vector   pgnGameTagsVector;
    java.awt.List     pgnGameList;
    java.awt.List     RAVList;
    Button   pgnButtonNext;
    Button   pgnButtonPrev;
    Button   pgnButtonStart;
    Button   pgnButtonEnd;
    Button   preferencesButton;
    Button   btnShowAnswer;
    String   strInitErrorMessage;
    String   strCurrentVariation;
    String   openingComment;
    ChessScoreKeeper scoreKeeper;
    Thread   animationThread;
    TextArea commentWindow;

    PgnGameTags currentGameTags;

    PopupMenu preferencesMenu;
    MenuItem  miFlipBoard;
    MenuItem  miAnimateMoves;
    MenuItem  miShowGamesList;

    public void init()
    {
        whiteFromBottom      = true;
        isDraggingOn         = false;
        isReadingPGN         = true;
        showPGNGameList      = false;
        bWhiteToMove         = true;
        bInitError           = false;
        bIsDownloading       = false;
        bAnimateMoves        = true;
        bWaitForFrameToPaint = false;
        bPaintBoardOnly      = false;
        bPuzzleMode          = getParameter("puzzlemode") != null && getParameter("puzzlemode").equals("on");
        bShowAnswer          = false;
        bShowSideOnMove      = false;
        bWindowClosed        = false;
        bWidget              = false;
        animatedSourceSquare = -1;
        animatedDestSquare   = -1;
        strInitErrorMessage  = "";
        intLastRAVMoveIndex  = 10000;

        // get the light square colors

        if (getParameter("lightSquares") == null)
        {
            squareLightColor = new Color(0xf0f0b8);
        }
        else
        {
            try
            {
                squareLightColor = new Color(Integer.parseInt(getParameter("lightSquares"), 16));
            }
            catch (Exception e)
            {
                squareLightColor = new Color(0xf0f0b8);
            }
        }

        // get the dark square colors

        if (getParameter("darkSquares") == null)
        {
            squareDarkColor  = new Color(0x009000);
        }
        else
        {
            try
            {
                squareDarkColor = new Color(Integer.parseInt(getParameter("darkSquares"), 16));
            }
            catch (Exception e)
            {
                squareDarkColor  = new Color(0x009000);
            }
        }

        // get the background color

        if (getParameter("background") == null)
        {
            backgroundColor = new Color(0x007000);
        }
        else
        {
            try
            {
                backgroundColor = new Color(Integer.parseInt(getParameter("background"), 16));
            }
            catch (Exception e)
            {
                backgroundColor = new Color(0x007000);
            }
        }

        // get the PGN file refresh interval

        if (getParameter("RefreshInterval") == null)
        {
            intRefreshInterval = 0;
        }
        else
        {
            try
            {
                intRefreshInterval = Integer.parseInt(getParameter("RefreshInterval"));
            }
            catch (Exception e)
            {
                intRefreshInterval = 0;
            }
        }
       
        if (getParameter("widget") != null&& getParameter("widget").equalsIgnoreCase("true")) {
            bWidget = true;
        }

        setLayout(null);

        // get our images

        MediaTracker tracker;

        tracker      = new MediaTracker(this);
        wPawnImage   = new ImageIcon(this.getClass().getResource("/images/wp.gif")).getImage();
                       tracker.addImage(wPawnImage, 0);
        wKnightImage = new ImageIcon(this.getClass().getResource("/images/wn.gif")).getImage();
                       tracker.addImage(wKnightImage, 0);
        wBishopImage = new ImageIcon(this.getClass().getResource("/images/wb.gif")).getImage();
                       tracker.addImage(wBishopImage, 0);
        wRookImage   = new ImageIcon(this.getClass().getResource("/images/wr.gif")).getImage();
                       tracker.addImage(wRookImage, 0);
        wQueenImage  = new ImageIcon(this.getClass().getResource("/images/wq.gif")).getImage();
                       tracker.addImage(wQueenImage, 0);
        wKingImage   = new ImageIcon(this.getClass().getResource("/images/wk.gif")).getImage();
                       tracker.addImage(wKingImage, 0);
        bPawnImage   = new ImageIcon(this.getClass().getResource("/images/bp.gif")).getImage();
                       tracker.addImage(bPawnImage, 0);
        bKnightImage = new ImageIcon(this.getClass().getResource("/images/bn.gif")).getImage();
                       tracker.addImage(bKnightImage, 0);
        bBishopImage = new ImageIcon(this.getClass().getResource("/images/bb.gif")).getImage();
                       tracker.addImage(bBishopImage, 0);
        bRookImage   = new ImageIcon(this.getClass().getResource("/images/br.gif")).getImage();
                       tracker.addImage(bRookImage, 0);
        bQueenImage  = new ImageIcon(this.getClass().getResource("/images/bq.gif")).getImage();
                       tracker.addImage(bQueenImage, 0);
        bKingImage   = new ImageIcon(this.getClass().getResource("/images/bk.gif")).getImage();
                       tracker.addImage(bKingImage, 0);

        // wait for those images

        try
        {
            tracker.waitForAll();
        }
        catch (Exception e)
        {
            strInitErrorMessage = e.toString();
            bInitError = true;
            return;
        }

        pieceOffsetX     = (squareWidth / 2) - (wPawnImage.getWidth(this) / 2);
        pieceOffsetY     = squareHeight - wPawnImage.getHeight(this);
        squarePressed    = -1;
        scoreKeeper      = new ChessScoreKeeper();

        // add listeners

        addMouseListener(this);
        addMouseMotionListener(this);

        // Initialize Tags

        currentGameTags = new PgnGameTags();

        currentGameTags.event       = "";
        currentGameTags.site        = "";
        currentGameTags.date        = "";
        currentGameTags.round       = "";
        currentGameTags.white       = "";
        currentGameTags.black       = "";
        currentGameTags.whiteRating = "";
        currentGameTags.blackRating = "";
        currentGameTags.result      = "";
        currentGameTags.ECOCode     = "";
        currentGameTags.FEN         = "";

        // add the comment window

        commentWindow = new TextArea();
        commentWindow.setBounds(bottomSpaceLeft + 10, bottomSpaceTop + 10, boardWidth - 20, bottomSpaceHeight - 20);
        commentWindow.setFont(new Font("SansSerif", Font.PLAIN, 13));
        commentWindow.setVisible(false);
        add(commentWindow);

        // add the RAV (variations) list

        RAVList = new java.awt.List();
        RAVList.setFont(new Font("SansSerif", Font.PLAIN, 12));
        RAVList.setSize(60,60);
        RAVList.setVisible(false);
        RAVList.addItemListener(this);
        add(RAVList);
    }

    public void stop()
    {
        // this stops the refresher thread (stops re-getting the pgn file)
        bWindowClosed = true;
    }

  public void destroy()
  {
    // this stops the refresher thread (stops re-getting the pgn file)
    bWindowClosed = true;
  }

    private void initPGN(boolean bFirstRead)
    {
        URL            pgnFileURL     = null;
        InputStream    pgnInputStream = null;
        ZipInputStream zipInputStream = null;
        BufferedReader pgnReader      = null;
        int            intGameNumber  = 0;

        // create and initialize gameTages
        PgnGameTags gameTags = new PgnGameTags();
        gameTags.event       = "";
        gameTags.site        = "";
        gameTags.date        = "";
        gameTags.round       = "";
        gameTags.white       = "";
        gameTags.black       = "";
        gameTags.whiteRating = "";
        gameTags.blackRating = "";
        gameTags.result      = "";
        gameTags.ECOCode     = "";
        gameTags.FEN         = "";

        // create the vectors that will hold our games and Tags

        if (bFirstRead)
        {
            pgnGames          = new Vector();
            pgnGameTagsVector = new Vector();
        }

        // get and open the pgn file

        try
        {
            pgnFileURL = new URL(getCodeBase(), getParameter("pgngamefile"));
        }
        catch (Exception e)
        {
            strInitErrorMessage = e.toString();
            bInitError = true;
            return;
        }

        if (getParameter("pgngamefile").endsWith(".zip") ||
            getParameter("pgngamefile").endsWith(".ZIP"))
        {
            try
            {
                zipInputStream = new ZipInputStream(pgnFileURL.openStream());
            }
            catch (Exception e)
            {
                strInitErrorMessage = e.toString();
                bInitError = true;
                return;
            }

            // position the stream on the entry data

            try
            {
                zipInputStream.getNextEntry();
            }
            catch (Exception e)
            {
                strInitErrorMessage = e.toString();
                bInitError = true;
                return;
            }

            pgnReader  = new BufferedReader(new InputStreamReader(zipInputStream));
        }
        else
        {
            try
            {
                pgnInputStream = pgnFileURL.openStream();
            }
            catch (Exception e)
            {
                strInitErrorMessage = e.toString();
                bInitError = true;
                return;
            }

            pgnReader  = new BufferedReader(new InputStreamReader(pgnInputStream));
        }

        // parse the pgn file for the game list

        String  lineOfText         = null;
        String  moveText           = "";
        boolean bIsReadingTagPairs = false;
        boolean bIsReadingMoveText = false;
        int     intGameCount       = 0;

        // when bIsDownLoading is true the message displaying
        // the bytes downloaded is painted - set this to false
        // when refreshing so the user won't be disturbed by
        // the this message

        bIsDownloading             = bFirstRead;
        int iTotalBytesDownloaded  = 0;
        for(;;)
        {
            // get a line of text

            try
            {
                lineOfText = pgnReader.readLine();
            }
            catch (Exception e)
            {
                strInitErrorMessage = e.toString();
                bInitError = true;
                return;
            }

            // end of file?

            if (lineOfText == null)
            {
                break;
            }

            // display the bytes downloaded every 1k bytes

            iTotalBytesDownloaded += lineOfText.length();
            if ((iTotalBytesDownloaded / 1024) != iKilobytesDownloaded)
            {
                iKilobytesDownloaded = iTotalBytesDownloaded / 1024;
                repaint();
            }

            // remove any leading or trailing white spaces

            lineOfText = lineOfText.trim();

            // blank line?

            if (lineOfText.length() == 0)
            {
                continue;
            }

            if (lineOfText.charAt(0) != '[')
            {
                // are we at the end of a tag-pair section?

                if (bIsReadingTagPairs)
                {
                    ++intGameNumber;

                    String pgnGameString = intGameNumber   + ". "   +
                                           gameTags.white  + " vs " +
                                           gameTags.black  + ", "   + "\"" +
                               gameTags.result + "\", " +
                               gameTags.date;

                    if (!gameTags.event.equals("?") && !gameTags.event.equals(""))
                    {
                        pgnGameString += (", " + gameTags.event);
                    }

                    if (!gameTags.site.equals("?") && !gameTags.site.equals(""))
                    {
                        pgnGameString += (", " + gameTags.site);
                    }

                    // if this is the first read, add the game

                    if (bFirstRead)
                    {
                        pgnGameList.add(pgnGameString);
                        pgnGameList.setVisible(false);
                        pgnGameTagsVector.addElement((PgnGameTags)gameTags.clone());
                    }

                    // if this is a refresh and it is an additional
                    // game add it

                    else if (intGameNumber > pgnGameTagsVector.size())
                    {
                        pgnGameList.add(pgnGameString);
                        pgnGameTagsVector.addElement((PgnGameTags)gameTags.clone());
                    }

                    // if this is a refresh and this is a different game
                    // replace it

                    else if (!pgnGameString.equals(pgnGameList.getItem(intGameNumber - 1)))
                    {
                        pgnGameList.replaceItem(pgnGameString, intGameNumber - 1);
                        pgnGameTagsVector.setElementAt((PgnGameTags)gameTags.clone(), intGameNumber - 1);
                    }

                    // erase our gameTags

                    gameTags.event       = "";
                    gameTags.site        = "";
                    gameTags.date        = "";
                    gameTags.round       = "";
                    gameTags.white       = "";
                    gameTags.black       = "";
                    gameTags.whiteRating = "";
                    gameTags.blackRating = "";
                    gameTags.result      = "";
                    gameTags.ECOCode     = "";
                    gameTags.FEN         = "";

                    // set this to false because the next blank
                    // line we are about to find will be at the end
                    // of the game moves.

                    bIsReadingTagPairs = false;

                    // this is the first line of the moveText section

                    moveText = "";
                    bIsReadingMoveText = true;
                }

                if (bIsReadingMoveText)
                {
                    // add the line to our game text

                    moveText += lineOfText + '\n';

                    // look for the end-of-game marker (1-0, 0-1, 1/2-1/2, or *)

                    if (lineOfText.endsWith("1-0")     ||
                        lineOfText.endsWith("0-1")     ||
                        lineOfText.endsWith("1/2-1/2") ||
                        lineOfText.endsWith("*"))
                    {
                        // if this is the first read, add the game

                        if (bFirstRead)
                        {
                            pgnGames.addElement(moveText);
                        }

                        // if this is a refresh and it is an additional
                        // game add it

                        else if (intGameNumber > pgnGames.size())
                        {
                            pgnGames.addElement(moveText);
                        }

                        // if this is a refresh and this is a different game
                        // replace it

                        else if (!moveText.equals((String)pgnGames.elementAt(intGameNumber - 1)))
                        {
                            pgnGames.setElementAt(moveText, intGameNumber - 1);
                        }

                        bIsReadingMoveText = false;
                        ++intGameCount;
                    }
                }

                continue;
            }

            bIsReadingTagPairs = true;

            // get game tags

            String param, value;
            param = lineOfText.substring(lineOfText.indexOf('[') + 1, lineOfText.indexOf('"'));
            value = lineOfText.substring(lineOfText.indexOf('"') + 1, lineOfText.lastIndexOf('"'));
            param = param.trim();
            value = value.trim();

            if (param.equals("Event"))
            {
                gameTags.event = value;
            }
            else if (param.equals("Site"))
            {
                gameTags.site = value;
            }
            else if (param.equals("Date"))
            {
                // convert yyyy.mm.dd to mm-dd-yyyy

                int iFirstDot  = value.indexOf('.');
                int iSecondDot = value.lastIndexOf('.');

                if (iFirstDot != -1 && iSecondDot != -1)
                {
                    gameTags.date = value.substring(iFirstDot + 1, iSecondDot)      + '-' +
                                           value.substring(iSecondDot + 1, value.length()) + '-' +
                                           value.substring(0, iFirstDot);
                }
                else
                {
                    gameTags.date = value;
                }
            }
            else if (param.equals("Round"))
            {
                gameTags.round = value;
            }
            else if (param.equals("White"))
            {
                gameTags.white = value;
            }
            else if (param.equals("Black"))
            {
                gameTags.black = value;
            }
            else if (param.equals("WhiteElo"))
            {
                gameTags.whiteRating = value;
            }
            else if (param.equals("BlackElo"))
            {
                gameTags.blackRating = value;
            }
            else if (param.equals("Result"))
            {
                gameTags.result = value;
            }
            else if (param.equals("ECO"))
            {
                gameTags.ECOCode = value;
            }
            else if (param.equals("FEN"))
            {
                gameTags.FEN = value;
            }
        }

        // close the stream

        try
        {
            pgnReader.close();
        }
        catch (Exception e)
        {
            strInitErrorMessage = e.toString();
            bInitError = true;
            return;
        }

        if (bFirstRead)
        {
            if (intGameCount == 1)
            {
                showPGNGameList = false;
                readPGNGame(0, false);
            }
             else
            {
                showPGNGameList = true;
            }

            bIsDownloading  = false;
            repaint();

            // set up our "Preferences" popup menu

            preferencesMenu = new PopupMenu();

            miAnimateMoves = new MenuItem("Turn Animation Off");
            miAnimateMoves.addActionListener(this);
            miAnimateMoves.setFont(new Font("Dialog", Font.PLAIN, 12));

            miFlipBoard = new MenuItem("Flip Board");
            miFlipBoard.addActionListener(this);
            miFlipBoard.setFont(new Font("Dialog", Font.PLAIN, 12));

            miShowGamesList = new MenuItem("Hide Games List");
            miShowGamesList.addActionListener(this);
            miShowGamesList.setFont(new Font("Dialog", Font.PLAIN, 12));

            preferencesMenu.add(miAnimateMoves);
            preferencesMenu.add(miFlipBoard);
            preferencesMenu.add(miShowGamesList);

            add(preferencesMenu);
        }
        else
        {
            // this is a refresh - call readPGNGame

            if (intGameCount == 1)
            {
                readPGNGame(0, true);
            }
            else
            {
                readPGNGame(pgnGameList.getSelectedIndex(), true);
            }
        }
    }

    private void readPGNGame(int gameNumber, boolean bIsRefresh)
    {
        // new game

        String theGame = "";
        String strComment = "";

        try
        {
            PgnGameTags tempTags = (PgnGameTags)pgnGameTagsVector.elementAt(gameNumber);
            currentGameTags = (PgnGameTags)tempTags.clone();
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            return;
        }

        try
        {
            theGame = new String((String)pgnGames.elementAt(gameNumber));
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            return;
        }

        ChessScoreKeeper tempScoreKeeper = new ChessScoreKeeper();

        // set the position if there is a FEN position;

        if (currentGameTags.FEN != null && !currentGameTags.FEN.equals(""))
        {
            tempScoreKeeper.setFEN(currentGameTags.FEN);
            whiteFromBottom = bWhiteToMove = tempScoreKeeper.isWhitesMove();

            // let the user know who is on move if we're in puzzle mode

            bShowSideOnMove = bPuzzleMode;
        }

        // add a space to the end of the game string (helps our parsing)

        theGame  = theGame.trim();
        theGame += " ";

        // parse that game!

        String  gameToken      = "";
        String  strMove        = "";
        boolean bIsCommentText = false;
        boolean bNewRAV        = false;
        Vector  vRAVPoint      = new Vector();
        FontMetrics fm         = commentWindow.getFontMetrics(commentWindow.getFont());
        for (int i = 0; i < theGame.length(); i++)
        {
            // all substrings are separated by a space

            if (Character.isWhitespace(theGame.charAt(i)))
            {
                // look for a real game move

                if (gameToken.length()  > 0      &&
                    gameToken.indexOf('}') == -1 &&
                    gameToken.indexOf(')') == -1 &&
                    bIsCommentText   == false    &&
                   (Character.isLetter(gameToken.charAt(0)) ||
                    gameToken.equals("0-0")      ||
                    gameToken.equals("0-0-0")    ||
                    gameToken.equals("1-0")      ||
                    gameToken.equals("1-0")      ||
                    gameToken.equals("1-0")      ||
                    gameToken.equals("1-0")      ||
                    gameToken.equals("0-1")      ||
                    gameToken.equals("1/2-1/2"||
                    gameToken.equals("*")))
                {
                    // this is a pgn move. Run it through and store it in the scoreKeeper
                    // (any valid move is at least 2 characters - except an asterisk *)

                    if (gameToken.length() > 1 || gameToken.equals("*"))
                    {
                        strMove = gameToken;

                        if (!tempScoreKeeper.makeMove(strMove, bNewRAV))
                        {
                            // illegal move - this game has been cut short

                            tempScoreKeeper.gotoPosition(0);
                            return;
                        }

                        // bNewRAV should always be false at this point

                        bNewRAV = false;
                    }
                }

                // get any comments that we might have

                else if (bIsCommentText)
                {
                    // this is a white character (probabaly a space)
                    // don't add the space if this is the first character
                    // of a new line (looks lousy)

                    if (strComment.lastIndexOf('\n') == strComment.length() - 1)
                    {
                        continue;
                    }

                    // add the space. Since we do our own word-wrapping, convert all
                    // white spaces to spaces

                    strComment += " ";

                    // do a little word-wrap hackage here

                    String strCommentLine;
                    if (strComment.lastIndexOf('\n') != -1)
                    {
                        strCommentLine = strComment.substring(strComment.lastIndexOf('\n') + 1, strComment.length());
                    }
                    else
                    {
                        strCommentLine = strComment.substring(0, strComment.length());
                    }

                    if (fm.stringWidth(strCommentLine) > commentWindow.getSize().width - fm.stringWidth("12345678"))
                    {
                        strComment += '\n';
                    }
                }

                // look for NAGs (Numeric Annotation Glyphs)

                else if (gameToken.length() > 1 && gameToken.charAt(0) == '$')
                {
                    int intNAGIndex;
                    try
                    {
                        intNAGIndex = Integer.parseInt(gameToken.substring(1, gameToken.length()));
                    }
                    catch (Exception e)
                    {
                        intNAGIndex = 0;
                    }

                    if (intNAGIndex < NAGStrings.length)
                    {
                      // Add Nag String - Only add a ' - ' if there is alreay a comment
                        ChessMove cm = tempScoreKeeper.getChessMoveAt(tempScoreKeeper.getCurrentMoveIndex() - 1);
                        cm.setComment(cm.getComment() + (cm.getComment().length() > 0 ? " - " : "") + NAGStrings[intNAGIndex]);
                    }
                }

                gameToken = "";
            }
            else
            {
                // are we begining or ending a comment?

                if (theGame.charAt(i) == '{')
                {
                    bIsCommentText = true;
                    continue;
                }
                else if (theGame.charAt(i) == '}')
                {
                    // add the comment to the last chess move
                    // first remove the braces

                    if (strComment.length() > 0)
                    {
                      if (!bNewRAV)
                      {
                          // attach the comment to the current move, before any pre-existing comment (i.e. NAG move)
                          // NB. will handle comment before first move differently
                          if ( tempScoreKeeper.getCurrentMoveIndex()>0 )
                          {
                ChessMove cm = tempScoreKeeper.getChessMoveAt(tempScoreKeeper.getCurrentMoveIndex() - 1);             
                cm.setComment(strComment + (cm.getComment().length() > 0 ? " - " : "") + cm.getComment());
              }
              else
              {
                // Make a note of opening comment as we will
                // attach this to the first move's comment.
                openingComment = strComment;
              }
            }
                    }

                    strComment = "";
                    bIsCommentText = false;
                    continue;
                }
                else if (bIsCommentText)
                {
                    strComment += theGame.charAt(i);
                    continue;
                }

                // are we starting a variation?

                if (theGame.charAt(i) == '(')
                {
                    // we are starting a new RAV (line)
                    // get the current index (we'll be returning here

                    int intIndexPoint = tempScoreKeeper.getCurrentMoveIndex();

                    // get the key of the last move

                    String strKey = tempScoreKeeper.getMoveKey(intIndexPoint - 1);

                    // get the color on move

                    String strOnMove = tempScoreKeeper.isWhitesMove() ? "white" : "black";

                    // "push" the index point and key on the vRAVPoint vector

                    vRAVPoint.addElement(intIndexPoint + " " + strKey + " " + strOnMove);

                    bNewRAV = true;

                    gameToken = "";
                    continue;
                }
                else if (theGame.charAt(i) == ')')
                {
                    // is this attached to the final variation move? ie - 'Nf3)'

                    if (gameToken.length() > 1 && Character.isLetter(gameToken.charAt(0)))
                    {
                        tempScoreKeeper.makeMove(gameToken, bNewRAV);
                    }

                    // look for NAGs (Numeric Annotation Glyphs) ie - '$18)

                    else if (gameToken.length() > 1 && gameToken.charAt(0) == '$')
                    {
                        int intNAGIndex;
                        try
                        {
                            intNAGIndex = Integer.parseInt(gameToken.substring(1, gameToken.length()));
                        }
                        catch (Exception e)
                        {
                            intNAGIndex = 0;
                        }

                        if (intNAGIndex < NAGStrings.length)
                        {
                          // Add Nag String - Only add a ' - ' if there is alreay a comment
                            ChessMove cm = tempScoreKeeper.getChessMoveAt(tempScoreKeeper.getCurrentMoveIndex() - 1);
                            cm.setComment(cm.getComment() + (cm.getComment().length() > 0 ? " - " : "") + NAGStrings[intNAGIndex]);
                        }
                    }

                    // mave sure we unset bNewRAV at this point

                    bNewRAV = false;

                    // end this line whith a "*"

                    tempScoreKeeper.makeMove("*", false);

                    // we just finished a RAV (line)
                    // jump back to our RAVpoint - "pop" it off vRAVPoint

                    String strRAVPoint = (String)vRAVPoint.lastElement();
                    vRAVPoint.removeElementAt(vRAVPoint.size() - 1);

                    // get our RAVPoint index and key and color on move

                    int    intIndex     = Integer.parseInt(strRAVPoint.substring(0, strRAVPoint.indexOf(' ')));
                    String strKey       = strRAVPoint.substring(strRAVPoint.indexOf(' ') + 1, strRAVPoint.lastIndexOf(' '));
                    String strColor     = strRAVPoint.substring(strRAVPoint.lastIndexOf(' ') + 1, strRAVPoint.length());
                    boolean bWhitesMove = strColor.equals("white");

                    // have the scorekeeper reset the line

                    tempScoreKeeper.setLine(intIndex, strKey, bWhitesMove);

                    gameToken = "";
                    continue;
                }

                // some PGN games don't add a space after the move number: '1.e4 '
                // instead of the usual '1. e4' - deal with it!

                else if (theGame.charAt(i) == '.')
                {
                    gameToken = "";
                    continue;
                }

                // add the character to our game token

                gameToken += theGame.charAt(i);
            }
        }

        // if this is a refresh and the games has changed
        // setup the new game to tempScoreKeeper

        if (bIsRefresh && !scoreKeeper.equals(tempScoreKeeper))
        {
            if (tempScoreKeeper.getTotalMoves() >= scoreKeeper.getTotalMoves())
            {
                tempScoreKeeper.gotoPosition(scoreKeeper.getCurrentMoveIndex());
                scoreKeeper = tempScoreKeeper;
            }
            else
            {
                scoreKeeper = tempScoreKeeper;
                scoreKeeper.gotoPosition(0);
            }

            repaint();
        }

        // if this is a new game goto the start
        // if this is a new game.

        else if (!bIsRefresh)
        {
            scoreKeeper = tempScoreKeeper;
            scoreKeeper.gotoPosition(0);
        }
       
        if (bWidget) {
          scoreKeeper.gotoPosition(getCurrentPosition());
        }
    }

    // this function updates the scoresheet by checking to see if the PGN
    // file has changed.   A web programmer can use this funtionality by
    // passing the parameter "RefreshInterval" with a value that specifies
    // the seconds to wait between refreshes.

    private void startRefresher()
    {
        if (intRefreshInterval == 0)
        {
            return;
        }

        initPGN(true);

        while (!bWindowClosed)
        {
            // sleep for the specified interval

            try
            {
                Thread.sleep(intRefreshInterval * 1000);
            }
            catch (Exception e)
            {
            }

            initPGN(false);
        }
    }

    private synchronized void animateMove()
    {
        final int   factor       = 5;
        int         fraction     = 1;
        Vector      framesVector = new Vector(factor * 2);
        final Point startPoint   = squareNumberToPoint(animatedSourceSquare);
        final Point endPoint     = squareNumberToPoint(animatedDestSquare);
        Point       midPoint     = new Point();

        // This logic is taken from Winboard.  The piece moves
        // in slow and speeds up towards the middle, then slows
        // down again toward the end.

        startPoint.x += pieceOffsetX;
        startPoint.y += pieceOffsetY;
        endPoint.x   += pieceOffsetX;
        endPoint.y   += pieceOffsetY;

        if (animatedPiece != 'N' && animatedPiece != 'n')
        {
            // all pieces, except knights, move in a straight line

            midPoint.x    = startPoint.x + ((endPoint.x - startPoint.x) / 2);
            midPoint.y    = startPoint.y + ((endPoint.y - startPoint.y) / 2);
        }
        else
        {
            // knights move straight and then diagonally

            midPoint.x = startPoint.x + (endPoint.x - startPoint.x) / 2;
            midPoint.y = endPoint.y;
        }

        for (int i = 0; i < factor; i++)
        {
            fraction *= 2;
        }

        for (int i = 0; i < factor; i++)
        {
            int x, y;

            // slow to fast - 1/16, 1/8, etc...

            x = startPoint.x + ((midPoint.x - startPoint.x) / fraction);
            y = startPoint.y + ((midPoint.y - startPoint.y) / fraction);
            framesVector.addElement(new Point(x, y));
            fraction /= 2;
        }

        fraction = 2;
        for (int i = 0; i < factor; i++)
        {
            int x, y;

            // fast to slow - 1/16, 1/8, etc...

            x = endPoint.x - ((endPoint.x - midPoint.x) / fraction);
            y = endPoint.y - ((endPoint.y - midPoint.y) / fraction);
            framesVector.addElement(new Point(x, y));
            fraction *= 2;
        }

        // add the final destination point

        framesVector.addElement(endPoint);

        animatedPieceX = -1;
        animatedPieceY = -1;
        long tm = System.currentTimeMillis();
        for (int i = 0; i < framesVector.size(); ++i)
        {
            Point point       = (Point)framesVector.elementAt(i);
            animatedPieceOldX = animatedPieceX;
            animatedPieceOldY = animatedPieceY;
            animatedPieceX    = point.x;
            animatedPieceY    = point.y;

            try
            {
                tm += 15;
                Thread.sleep(Math.max(0, tm - System.currentTimeMillis()));
            }
            catch (InterruptedException e)
            {
            }

            // wait for up to 1/10th of a second for the last frame
            // to finish painting (necessary on slow machines and slow
            // VM's such as Netscape 4.06).  bWaitForFrameToPaint is
            // set to false by update() once the frame is drawn.

            for (int j = 0; j < 20; j++)
            {
                if (!bWaitForFrameToPaint)
                {
                    break;
                }

                try
                {
                    Thread.sleep(5);
                }
                catch (Exception e)
                {
                }
            }

            // setting a lower priority before repainting eliminates flicker
            // it does, however, slow down animation quite a bit - but it's
            // the best I can figure our for now

            Thread.currentThread().setPriority(Thread.currentThread().getPriority() - 1);
            bWaitForFrameToPaint = true;
            repaint();
            Thread.currentThread().setPriority(Thread.currentThread().getPriority() + 1);
        }

        isAnimation = false;

        Thread.currentThread().setPriority(Thread.currentThread().getPriority() - 1);
        bPaintBoardOnly = true;
        repaint();
        Thread.currentThread().setPriority(Thread.currentThread().getPriority() + 1);

        // did the user just press the "next move" button from a puzzle?

        if (bPuzzleMode && !bShowAnswer)
        {
            bShowSideOnMove = false;
            bShowAnswer     = true;
            btnShowAnswer.setVisible(false);
            repaint();
        }
    }

    public void run()
    {
        if (Thread.currentThread().getName().equals("Animation"))
        {
            animateMove();
        }
        else if (Thread.currentThread().getName().equals("InitPgn"))
        {
            initPGN(true);
        }
        else if (Thread.currentThread().getName().equals("startRefresher"))
        {
            startRefresher();
        }
    }

    private void paintAnimation(Graphics g)
    {
        int pieceWidth = wPawnImage.getWidth(this);
        int pieceHeight = wPawnImage.getHeight(this);

        int square1 = xyToSquareNumber(animatedPieceOldX, animatedPieceOldY);
        int square2 = xyToSquareNumber(animatedPieceOldX + pieceWidth, animatedPieceOldY);
        int square3 = xyToSquareNumber(animatedPieceOldX, animatedPieceOldY + pieceHeight);
        int square4 = xyToSquareNumber(animatedPieceOldX + pieceWidth, animatedPieceOldY + pieceHeight);

        // repaint the necessary squares affected by our last animation frame

        repaintSquare(g, square1);
        if (square2 != square1)
        {
            repaintSquare(g, square2);
        }
        if (square3 != square2 && square3 != square1)
        {
            repaintSquare(g, square3);
        }
        if (square4 != square3 && square4 != square2 && square4 != square1)
        {
            repaintSquare(g, square4);
        }

        // now draw the animation piece

        drawPiece(g, animatedPiece, animatedPieceX, animatedPieceY);
    }

    private void repaintSquare(Graphics g, int square)
    {
        if (square < 0 || square > 63)
        {
            return;
        }

        Point point = squareNumberToPoint(square);

        // draw the square

        g.setColor(scoreKeeper.isLightSquare(square) ? squareLightColor : squareDarkColor);
        g.fillRect(point.x, point.y, squareWidth, squareHeight);

        // now draw whatever piece that might be there

        if (animationPosition[square] != '-')
        {
            drawPiece(g, animationPosition[square], point.x + pieceOffsetX, point.y + pieceOffsetY);
        }
    }

    public void update(Graphics g)
    {
        int file, rank;

        file = 1;
        rank = 1;
        Graphics offGraphics;

        if (offImage == null)
        {
            offImage = this.createImage(getSize().width, getSize().height);
        }

        offGraphics  = offImage.getGraphics();

        if (isAnimation)
        {
            paintAnimation(offGraphics);
            g.drawImage(offImage, 0, 0, this);
            bWaitForFrameToPaint = false;
            return;
        }
        else if (bIsDownloading)
        {
            drawMessageBox(offGraphics,
                           "Loading " + getParameter("pgngamefile") +
                           "\nPlease wait..." +
                           "\n(" + iKilobytesDownloaded + "k)",
                           Color.black,
                           Color.cyan,
                           boardWidth  / 2,
                           boardHeight / 2);

            g.drawImage(offImage, 0, 0, this);
            return;
        }

        // set the background to the dark square color

        offGraphics.setColor(squareDarkColor);
        offGraphics.fillRect(0, 0, boardWidth, boardHeight);

        // draw the light squares here (by an inner background rect)

        offGraphics.setColor(squareLightColor);
        offGraphics.fillRect(leftMargin   - 2,
                             topMargin    - 2,
                            (squareWidth  * 8) + 4,
                            (squareHeight * 8) + 4);

        offGraphics.setColor(squareDarkColor);
        for (int i = 0; i < 64; i++)
        {
            // Calculate x, y for the squares

            int x = leftMargin + (squareWidth  * (file - 1));
            int y = topMargin  + (squareHeight * (rank - 1));

            // draw a dark square (white squares are already there - see above)

            if ((file + rank) % 2 != 0)
            {
                offGraphics.fillRect(x, y, squareWidth, squareHeight);
            }

            // draw the piece at this square (if any)

            char position[] = !isAnimation ? scoreKeeper.getCurrentPosition() :
                                             animationPosition;

            char pieceToDraw = whiteFromBottom ? position[i] : position[63 - i];
            drawPiece(offGraphics, pieceToDraw, x + pieceOffsetX, y + pieceOffsetY);

            // next rank ?

            if (file == 8 )
            {
                file = 1;
                ++rank;
            }
            else
            {
                ++file;
            }
        }

        // draw algrebraec notation character markers

        drawNotationMarkers(offGraphics);

        // show any comments we may have

        String strMoveComment = "";
        if (scoreKeeper.getCurrentMoveIndex() > 0)
        {
            strMoveComment = scoreKeeper.getChessMoveAt(scoreKeeper.getCurrentMoveIndex() - 1).getComment();

      // if there is an opening comment - prepend it to the first move's comment
      if (openingComment != null && scoreKeeper.getCurrentMoveIndex() == 1)
        strMoveComment = openingComment + "\n\n" + strMoveComment;

      commentWindow.setText(strMoveComment);
        }

        if (!strMoveComment.equals(""))
        {
            if (pgnGameList.isVisible())
            {
                pgnGameList.setVisible(false);
            }

            commentWindow.setVisible(true);
        }
        else
        {
            commentWindow.setVisible(false);

            if (showPGNGameList)
            {
                pgnGameList.setVisible(true);
            }
        }

        if (bPaintBoardOnly)
        {
            updateHighlightedMove(offGraphics);
            g.drawImage(offImage, 0, 0, this);
            bPaintBoardOnly = false;
            return;
        }

        // draw the rightSpace (scoresheet stuff)

        drawRightSpace(offGraphics);

        // draw the bottom space (where the game list, etc., lives)

        /*if (pgnGameList != null &&
            getComponentAt(bottomSpaceLeft + 4, bottomSpaceTop + 4) != pgnGameList)
        {
            // let the user know it will take a moment to draw the
            // game list and buttons

            drawMessageBox(offGraphics,
                           "One moment...",
                           Color.black,
                           Color.cyan,
                           boardWidth  / 2,
                           boardHeight / 2);

            g.drawImage(offImage, 0, 0, this);
        }*/

        drawBottomSpace(offGraphics);

        // slap down the offscreen image

        g.drawImage(offImage, 0, 0, this);

        if (isReadingPGN && pgnGameList == null)
        {
            pgnGameList = new java.awt.List();
            pgnGameList.addItemListener(this);

            iKilobytesDownloaded = 0;

            if (intRefreshInterval > 0)
            {
                Thread refresherThread = new Thread(this, "startRefresher");
                refresherThread.start();
            }
            else
            {
                Thread initPgnThread = new Thread(this, "InitPgn");
                initPgnThread.start();
            }
        }
    }

    // display the variation list if we have..well..a variation

    private void CheckForRAVS()
    {
        int intMoveIndex = scoreKeeper.getCurrentMoveIndex();

        if (intMoveIndex == intLastRAVMoveIndex)
        {
            return;
        }

        intLastRAVMoveIndex = intMoveIndex;

        // empty the list (list.removall all seems to be broken in EI5)

        while (RAVList.getItemCount() > 0)
        {
            RAVList.remove(0);
        }

        if (intMoveIndex == -1 || scoreKeeper.variationsAt(intMoveIndex) == 0)
        {
            intLastRAVMoveIndex = 10000;
            RAVList.setVisible(false);
            return;
        }

        // add this move's variations

        Vector vRAVS = scoreKeeper.getVariationsAt(intMoveIndex);

        for (int i = 0; i < vRAVS.size(); ++i)
        {
            RAVList.add((String)vRAVS.elementAt(i));      // addItem is deprecated
        }

        RAVList.setVisible(true);
    }

    public void paint(Graphics g)
    {
        update(g);
    }

    void drawNotationMarkers(Graphics g)
    {
        // set font info

        g.setFont(new Font("Dialog", Font.BOLD, 12));
        FontMetrics fm = g.getFontMetrics();

        g.setColor(squareLightColor);

        // draw algrebraec notation characters here

        char fileChar, rankChar;
        for (int i = 0; i < 8; i++)
        {
            if (whiteFromBottom)
            {
                fileChar = (char)('a' + i);
                rankChar = (char)('8' - i);
            }
            else
            {
                fileChar = (char)('h' - i);
                rankChar = (char)('1' + i);
            }

            // draw the file characters (a - h) in the bottom margin

            g.drawString("" + fileChar,
                         leftMargin + (squareWidth * i) +
                                      (squareWidth /2 ) -
                                      (fm.charWidth(fileChar) /2),
                         topMargin  + (squareHeight * 8) + 4 +
                                       fm.getAscent());

            // draw the rank characters (1 - 8) in the left margin

            g.drawString("" + rankChar,
                        (leftMargin - 7) - fm.charWidth(rankChar),
                         topMargin + (squareHeight * i) +
                                     (squareHeight / 2) +
                                     (fm.getAscent() / 2));
        }
    }

    void drawRightSpace(Graphics g)
    {

        // draw the rightSpace's background

        g.setColor(backgroundColor);
        g.fillRect(rightSpaceLeft,
                   rightSpaceTop,
                   rightSpaceWidth,
                   rightSpaceHeight);

        // draw a boarder around that background

        g.setColor(new Color(0x000000));
        g.drawRect(rightSpaceLeft,
                   rightSpaceTop,
                   rightSpaceWidth - 1,
                   rightSpaceHeight);

        // draw pager-like background over the above background

        g.setColor(new Color(0xfffff0));
        g.fillRect(scoreSheetLeft,
                   scoreSheetTop,
                   scoreSheetWidth,
                   scoreSheetHeight);

        // draw a boarder around that background

        g.setColor(new Color(0x000000));
        g.drawRect(scoreSheetLeft,
                   scoreSheetTop,
                   scoreSheetWidth - 1,
                   scoreSheetHeight);


        // draw the scoreSheet form
        // event and year are on the same line

        final int dateTagBoxWidth  = 70;
        final int eventTagBoxWidth = scoreSheetWidth - dateTagBoxWidth;
        final int eventTagBoxLeft  = scoreSheetLeft;
        final int dateTagBoxLeft   = eventTagBoxLeft + eventTagBoxWidth;

        // site, round, result, and ECO code are on this line

        final int ecoTagBoxWidth    = 30;
        final int resultTagBoxWidth = 27;
        final int roundTagBoxWidth  = 20;
        final int siteTagBoxWidth   = scoreSheetWidth - ecoTagBoxWidth - roundTagBoxWidth - resultTagBoxWidth;
        final int siteTagBoxLeft    = scoreSheetLeft;
        final int roundTagBoxLeft   = scoreSheetLeft + siteTagBoxWidth;
        final int resultTagBoxLeft  = scoreSheetLeft + siteTagBoxWidth + roundTagBoxWidth;
        final int ecoTagBoxLeft     = scoreSheetLeft + siteTagBoxWidth + roundTagBoxWidth + resultTagBoxWidth;

        // white player and rating are on this line

        final int whiteRatingTagBoxWidth = 40;
        final int whiteTagBoxWidth       = scoreSheetWidth - whiteRatingTagBoxWidth;
        final int whiteTagBoxLeft        = scoreSheetLeft;
        final int whiteRatingTagBoxLeft  = whiteTagBoxLeft + whiteTagBoxWidth;

        // black player and rating are on this line

        final int blackRatingTagBoxWidth = 40;
        final int blackTagBoxWidth       = scoreSheetWidth - blackRatingTagBoxWidth;
        final int blackTagBoxLeft        = scoreSheetLeft;
        final int blackRatingTagBoxLeft  = blackTagBoxLeft + blackTagBoxWidth;

        // draw the tag labels

        g.setFont(new Font("Monospaced", Font.PLAIN, 9));
        FontMetrics fm = g.getFontMetrics();
        g.setColor(Color.black);

        int y = scoreSheetTop;
        g.drawString("event", eventTagBoxLeft + 1, y + fm.getAscent());
        g.drawString("date",  dateTagBoxLeft  + 1, y + fm.getAscent());

        y += tagBoxHeight;
        g.drawString("site",  siteTagBoxLeft   + 1, y + fm.getAscent());
        g.drawString("rnd",   roundTagBoxLeft  + 1, y + fm.getAscent());
        g.drawString("score", resultTagBoxLeft + 1, y + fm.getAscent());
        g.drawString("eco",   ecoTagBoxLeft    + 1, y + fm.getAscent());

        y += tagBoxHeight;
        g.drawString("white",  whiteTagBoxLeft + 1,        y + fm.getAscent());
        g.drawString("rating", whiteRatingTagBoxLeft  + 1, y + fm.getAscent());

        y += tagBoxHeight;
        g.drawString("black",  blackTagBoxLeft + 1,        y + fm.getAscent());
        g.drawString("rating", blackRatingTagBoxLeft  + 1, y + fm.getAscent());

        // now draw the tag boxes

        g.setColor(Color.black);

        y = scoreSheetTop;
        g.drawRect(eventTagBoxLeft, y, eventTagBoxWidth,    tagBoxHeight);
        g.drawRect(dateTagBoxLeft,  y, dateTagBoxWidth - 1, tagBoxHeight);

        y += tagBoxHeight;
        g.drawRect(siteTagBoxLeft,   y, siteTagBoxWidth,    tagBoxHeight);
        g.drawRect(roundTagBoxLeft,  y, roundTagBoxWidth,   tagBoxHeight);
        g.drawRect(resultTagBoxLeft, y, resultTagBoxWidth,  tagBoxHeight);
        g.drawRect(ecoTagBoxLeft,    y, ecoTagBoxWidth - 1, tagBoxHeight);

        y += tagBoxHeight;
        g.drawRect(whiteTagBoxLeft,        y, whiteTagBoxWidth,           tagBoxHeight);
        g.drawRect(whiteRatingTagBoxLeft,  y, whiteRatingTagBoxWidth - 1, tagBoxHeight);

        y += tagBoxHeight;
        g.drawRect(blackTagBoxLeft,        y, blackTagBoxWidth,           tagBoxHeight);
        g.drawRect(blackRatingTagBoxLeft,  y, blackRatingTagBoxWidth - 1, tagBoxHeight);

        // draw the tags values

        g.setFont(new Font("SansSerif", Font.PLAIN, 12));
        fm = g.getFontMetrics();
        g.setColor(Color.darkGray);

        y = scoreSheetTop + tagBoxHeight;
        g.drawString(currentGameTags.event,
                     eventTagBoxLeft + ((eventTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.event) / 2)),
                     y - 4);
        g.drawString(currentGameTags.date,
                     dateTagBoxLeft + ((dateTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.date) / 2)),
                     y - 4);

        y += tagBoxHeight;
        g.drawString(currentGameTags.site,
                     siteTagBoxLeft + ((siteTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.site) / 2)),
                     y - 4);
        g.drawString(currentGameTags.round,
                     roundTagBoxLeft + ((roundTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.round) / 2)),
                     y - 4);
        g.drawString(currentGameTags.result.equals("1/2-1/2") ? "1/2" : currentGameTags.result,
                     resultTagBoxLeft + ((resultTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.result.equals("1/2-1/2") ? "1/2" : currentGameTags.result) / 2)),
                     y - 4);
        g.drawString(currentGameTags.ECOCode,
                     ecoTagBoxLeft + ((ecoTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.ECOCode) / 2)),
                     y - 4);

        y += tagBoxHeight;
        g.drawString(currentGameTags.white,
                     whiteTagBoxLeft + ((whiteTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.white) / 2)),
                     y - 4);
        g.drawString(currentGameTags.whiteRating,
                     whiteRatingTagBoxLeft + ((whiteRatingTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.whiteRating) / 2)),
                     y - 4);

        y += tagBoxHeight;
        g.drawString(currentGameTags.black,
                     blackTagBoxLeft + ((blackTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.black) / 2)),
                     y - 4);
        g.drawString(currentGameTags.blackRating,
                     blackRatingTagBoxLeft + ((blackRatingTagBoxWidth / 2) - (fm.stringWidth(currentGameTags.blackRating) / 2)),
                     y - 4);

        // now to the area where we'll be actually show chess moves
        // draw a horizontal line for form one "bold" line between the
        // tag headers secions and the moves section

        g.drawLine(scoreSheetLeft, scoreSheetMovesTop - 1, scoreSheetLeft + scoreSheetWidth - 1, scoreSheetMovesTop - 1);

        // draw the move boxes - vertical lines first

        y = scoreSheetMovesTop;
        g.drawLine(whitesLeftColLeft,  y, whitesLeftColLeft,  scoreSheetTop + scoreSheetHeight);
        g.drawLine(blacksLeftColLeft,  y, blacksLeftColLeft,  scoreSheetTop + scoreSheetHeight);
        g.drawLine(scoreSheetMiddle,   y, scoreSheetMiddle,   scoreSheetTop + scoreSheetHeight);
        g.drawLine(whitesRightColLeft, y, whitesRightColLeft, scoreSheetTop + scoreSheetHeight);
        g.drawLine(blacksRightColLeft, y, blacksRightColLeft, scoreSheetTop + scoreSheetHeight);

        // draw the horizontal lines and move numbers

        g.setFont(new Font("Monospaced", Font.PLAIN, 10));
        fm = g.getFontMetrics();
        g.setColor(Color.black);

        // what scoresheet page is this?

        int movesPerPage = moveLines * 4;
        int page = (scoreKeeper.getCurrentMoveIndex() - 1) / movesPerPage;

        for (int i = 1; i <= moveLines; i++)
        {
            int moveNumber;
            moveNumber = (page * (movesPerPage / 2)) + i;

            y += moveBoxHeight;
            g.drawLine(scoreSheetLeft, y, scoreSheetLeft + scoreSheetWidth - 1, y);

            // the left sid move numbers

            g.drawString(String.valueOf(moveNumber),
                         scoreSheetLeft + (boxForMoveNumsWidth / 2) - (fm.stringWidth(String.valueOf(moveNumber)) / 2),
                         y - 3);

            // the right side move numbers

            g.drawString(String.valueOf(moveNumber + moveLines),
                         scoreSheetMiddle + (boxForMoveNumsWidth / 2) - (fm.stringWidth(String.valueOf(moveNumber + moveLines)) / 2),
                         y - 3);
        }

        // if this is puzzle mode don't show the moves unless bShowAnswer == true

        if (bPuzzleMode && !bShowAnswer)
        {
            return;
        }

        // now draw the moves of the game

        g.setFont(new Font("SansSerif", Font.PLAIN, 12));
        fm = g.getFontMetrics();
        g.setColor(Color.darkGray);


        y = scoreSheetMovesTop;
        strCurrentVariation = null;
        for (int i = 0; i < movesPerPage; i++)
        {
            int moveIndex;
            moveIndex = (page * movesPerPage) + i;

            if (moveIndex >= scoreKeeper.getTotalMoves())
            {
                break;
            }

            String pgnMove = scoreKeeper.getPGNMoveAt(moveIndex);

            // if this move is the starting of a variation, make it blue

            if (scoreKeeper.isVariationAt(moveIndex + 1))
            {
                // if this is a variation on the main line - draw strCurrentVariation

                if (scoreKeeper.isMainLineVariationAt(moveIndex + 1))
                {
                    // this is the variation off the main line

                    strCurrentVariation = "Variation: " + (moveIndex + 3) / 2 + ". ";

                    if (moveIndex % 2 == 0)
                    {
                        strCurrentVariation += "... ";
                    }

                    strCurrentVariation += scoreKeeper.getPGNMoveAt(moveIndex + 1);

                    // draw the variation string if this is not the main line

                    g.setFont(new Font("Dialog", Font.BOLD, 12));
                    fm = g.getFontMetrics();
                    g.setColor(squareLightColor);

                    int x1 = ((leftMargin + boardWidth) / 2) - (fm.stringWidth(strCurrentVariation) / 2);
                    int y1 = topMargin - 8 ;
                    g.drawString(strCurrentVariation, x1, y1);

                    // reset font

                    g.setFont(new Font("SansSerif", Font.PLAIN, 12));
                    fm = g.getFontMetrics();
                }

                g.setColor(Color.blue);
            }
            else if (g.getColor() == Color.blue)
            {
                g.setColor(Color.darkGray);
            }

            // if there are variations at the next move add "*", "**", or "***"
            // to the move string - 'Bg5' might be '*Bg5', or 'Bg5**"

            int intVariations = scoreKeeper.variationsAt(moveIndex + 1);
            if (intVariations == 1)
            {
                pgnMove = pgnMove + "*";
            }
            if (intVariations == 2)
            {
                pgnMove = pgnMove + "**";
            }
            if (intVariations > 2)
            {
                pgnMove = pgnMove + "***";
            }

            // CheckForRAVS turns on and off the RAVS list

            CheckForRAVS();

      // if we started from a FEN position - black may have had the first move
      int currentIndex = scoreKeeper.blackHadFirstMove() ? i + 1 : i;

      if (currentIndex == moveLines * 2)
      {
        y = scoreSheetMovesTop;
      }

      // need to move down a line of this is the first move and black is on move (from a FEN position)
      if (scoreKeeper.blackHadFirstMove() && moveIndex == 0)
      {
        y += moveBoxHeight;     

        // draw a "..." before black's first move (in white's column)
        g.drawString("...",
                   whitesLeftColLeft + (moveBoxWidth / 2) - (fm.stringWidth("...") / 2),
                   y - 2);
      }

      // if this is the current move - make it bold
      if (moveIndex == scoreKeeper.getCurrentMoveIndex() - 1)
      {
        g.setFont(new Font("SansSerif", Font.BOLD, 12));
        fm = g.getFontMetrics();
      }
      else if (pgnMove.endsWith("err"))
      {
        g.setColor(Color.red);
      }
      else if (fm.getFont().isBold())
      {
        // else turn off the Bold if it was on

        g.setFont(new Font("SansSerif", Font.PLAIN, 12));
        fm = g.getFontMetrics();
      }

            if (currentIndex % 2 == 0)
            {
                // this is a white move
                y += moveBoxHeight;
                if (currentIndex < moveLines * 2)
                {
                    // left side of the scoresheet

                    g.drawString(pgnMove,
                                 whitesLeftColLeft + (moveBoxWidth / 2) - (fm.stringWidth(pgnMove) / 2),
                                 y - 2);
                }
                else
                {
                    // right side of the scoresheet

                    g.drawString(pgnMove,
                                 whitesRightColLeft + (moveBoxWidth / 2) - (fm.stringWidth(pgnMove) / 2),
                                 y - 2);
                }
            }
            else
            {
                // this is a black move

                if (currentIndex < moveLines * 2)
                {
                    // left side of the scoresheet

                    g.drawString(pgnMove,
                                 blacksLeftColLeft + (moveBoxWidth / 2) - (fm.stringWidth(pgnMove) / 2),
                                 y - 2);
                }
                else
                {
                    // right side of the scoresheet

                    g.drawString(pgnMove,
                                 blacksRightColLeft + (moveBoxWidth / 2) - (fm.stringWidth(pgnMove) / 2),
                                 y - 2);
                }
            }
        }
    }  

    private void updateHighlightedMove(Graphics g)
    {

        // if we're starting a new page redraw the scoresheet

        if ((scoreKeeper.getCurrentMoveIndex() - 1) % (moveLines * 4) == 0)
        {
              drawRightSpace(g);
              return;
        }

        int    iMoveIndex = scoreKeeper.getCurrentMoveIndex() -1;
        int    iLastIndex = iMoveIndex - 1;
        String pgnMove    = scoreKeeper.getPGNMoveAt(iMoveIndex);
        String pgnOldMove = scoreKeeper.getPGNMoveAt(iMoveIndex - 1);

        // if there are variations at the next move add "*", "**", or "***"
        // to the move string - 'Bg5' might be 'Bg5*', or 'Bg5**"

        int intVariations = scoreKeeper.variationsAt(iMoveIndex + 1);
        if (intVariations == 1)
        {
            pgnMove = pgnMove + "*";
        }
        if (intVariations == 2)
        {
            pgnMove = pgnMove + "**";
        }
        if (intVariations > 2)
        {
            pgnMove = pgnMove + "***";
        }

        intVariations = scoreKeeper.variationsAt(iLastIndex + 1);
        if (intVariations == 1)
        {
            pgnOldMove = pgnOldMove + "*";
        }
        if (intVariations == 2)
        {
            pgnOldMove = pgnOldMove + "**";
        }
        if (intVariations > 2)
        {
            pgnOldMove = pgnOldMove + "***";
        }

        // bump the index (for rending only) if black had the first move (FEN position)
    if (scoreKeeper.blackHadFirstMove())
    {
      iMoveIndex++;
      iLastIndex++;
    }

        // calculate y - I know this is difficult to decipher...sorry Charlie       

        int iLastMoveX    = 0;
        int iCurrentMoveX = 0;
        int iCurrentMoveY = scoreSheetMovesTop + ((((iMoveIndex % (moveLines * 2)) - (iMoveIndex % 2)) / 2) * moveBoxHeight) + moveBoxHeight;
        int iLastMoveY    = scoreSheetMovesTop + ((((iLastIndex % (moveLines * 2)) - (iLastIndex % 2)) / 2) * moveBoxHeight) + moveBoxHeight;

        // erase the old moves

        iMoveIndex = iMoveIndex % (moveLines * 4);
        iLastIndex = iLastIndex % (moveLines * 4);

        if (iMoveIndex % 2 == 0)
        {
            // this is a white move

            iCurrentMoveX = iMoveIndex < moveLines * 2 ? whitesLeftColLeft : whitesRightColLeft;
            iLastMoveX    = iLastIndex < moveLines * 2 ? blacksLeftColLeft : blacksRightColLeft;
        }
        else
        {
            // this is a black move

            iCurrentMoveX = iMoveIndex < moveLines * 2 ? blacksLeftColLeft : blacksRightColLeft;
            iLastMoveX    = iLastIndex < moveLines * 2 ? whitesLeftColLeft : whitesRightColLeft;
        }

        // remove the old highLighted move

        g.setColor(new Color(0xfffff0));
        g.fillRect(iLastMoveX + 1,  iLastMoveY - moveBoxHeight + 1, moveBoxWidth - 2, moveBoxHeight - 2);

        // draw the old move PLAIN

        g.setFont(new Font("SansSerif", Font.PLAIN, 12));
        FontMetrics fm = g.getFontMetrics();
        g.setColor(scoreKeeper.isVariationAt(scoreKeeper.getCurrentMoveIndex() - 1) ? Color.blue : Color.darkGray);
        g.drawString(pgnOldMove,
                     iLastMoveX + (moveBoxWidth / 2) - (fm.stringWidth(pgnOldMove) / 2),
                     iLastMoveY - 2);


        // erase the old move with a rectangle

        g.setColor(new Color(0xfffff0));
        g.fillRect(iCurrentMoveX + 1,  iCurrentMoveY - moveBoxHeight + 1, moveBoxWidth - 2, moveBoxHeight - 2);

        // draw the move in BOLD

        g.setFont(new Font("SansSerif", Font.BOLD, 12));
        fm = g.getFontMetrics();
        g.setColor(scoreKeeper.isVariationAt(scoreKeeper.getCurrentMoveIndex()) ? Color.blue : Color.darkGray);
        g.drawString(pgnMove,
                     iCurrentMoveX + (moveBoxWidth / 2) - (fm.stringWidth(pgnMove) / 2),
                     iCurrentMoveY - 2);

        // draw the variation string if this is not the main line

        if (strCurrentVariation != null)
        {
            g.setFont(new Font("Dialog", Font.BOLD, 12));
            fm = g.getFontMetrics();
            g.setColor(squareLightColor);

            int x1 = ((leftMargin + boardWidth) / 2) - (fm.stringWidth(strCurrentVariation) / 2);
            int y1 = topMargin - 8 ;
            g.drawString(strCurrentVariation, x1, y1);

        }

        // CheckForRAVS turns on and off the RAVS list

        CheckForRAVS();
    }

    private void drawBottomSpace(Graphics g)
    {
        // draw the scoresheet's background

        g.setColor(backgroundColor);
        g.fillRect(bottomSpaceLeft,
                   bottomSpaceTop,
                   bottomSpaceWidth,
                   bottomSpaceHeight);

        // draw a boarder around that background

        g.setColor(new Color(0x000000));
        g.drawRect(bottomSpaceLeft,
                   bottomSpaceTop,
                   bottomSpaceWidth  - 1,
                   bottomSpaceHeight - 1);

        // draw the game list if we are reading a PGN file

        int pgnGameListX = bottomSpaceLeft + 4;
        int pgnGameListY = bottomSpaceTop  + 4;

        // draw "www.MyChess.com" - my credit
        g.setFont(new Font("Dialog", Font.PLAIN, 13));
        FontMetrics fm = g.getFontMetrics();
        String strMyCredit;
        if (bWidget) {
          strMyCredit = new String("Ozone channel: mychess");
        }
        else {
          strMyCredit = new String("www.mychess.com");         
        }

        g.setColor(squareLightColor);
        g.drawString(strMyCredit,
                     rightSpaceLeft + (rightSpaceWidth / 2) - (fm.stringWidth(strMyCredit) / 2),
                     bottomSpaceTop + bottomSpaceHeight - (fm.getHeight() / 2));

        if (bInitError)
        {
            drawErrorMessage(g, strInitErrorMessage,
                             bottomSpaceLeft + (bottomSpaceWidth  / 2),
                             bottomSpaceTop  + (bottomSpaceHeight / 2));
            return;
        }
        else if (showPGNGameList)
        {
            // do we need to add the game list to this container (applet)?

            if (pgnGameList != null &&
                getComponentAt(pgnGameListX, pgnGameListY) != pgnGameList)
            {
                add(pgnGameList);
                pgnGameList.setBounds(pgnGameListX,
                                      pgnGameListY,
                                      boardWidth - 8,
                                      bottomSpaceHeight - 8);

                pgnGameList.setVisible(true);
            }
        }

        // draw our buttons that control game viewing

              int buttonWidth  = 40;
        final int buttonHeight = 20;
        final int nextButtonX  = buttonWidth + 5;
        final int nextButtonY  = buttonHeight + 10;
        int       buttonLeft   = rightSpaceLeft + (rightSpaceWidth / 2)   - ((buttonWidth + (nextButtonX * 3)) / 2);
        int       buttonTop    = bottomSpaceTop + 5;

        if (getComponentAt(buttonLeft, buttonTop) != pgnButtonStart)
        {
            pgnButtonNext     = new Button(">");
            pgnButtonPrev     = new Button("<");
            pgnButtonStart    = new Button("<<");
            pgnButtonEnd      = new Button(">>");
            preferencesButton = new Button("Preferences");
            btnShowAnswer     = new Button("Show Answer");
            pgnButtonNext.addActionListener(this);
            pgnButtonPrev.addActionListener(this);
            pgnButtonStart.addActionListener(this);
            pgnButtonEnd.addActionListener(this);
            preferencesButton.addActionListener(this);
            btnShowAnswer.addActionListener(this);
            add(pgnButtonStart);
            add(pgnButtonPrev);
            add(pgnButtonNext);
            add(pgnButtonEnd);
            add(preferencesButton);
            add(btnShowAnswer);
            pgnButtonStart.setBounds(buttonLeft, buttonTop, buttonWidth, buttonHeight);
            buttonLeft += nextButtonX;
            pgnButtonPrev.setBounds (buttonLeft, buttonTop, buttonWidth, buttonHeight);
            buttonLeft += nextButtonX;
            pgnButtonNext.setBounds (buttonLeft, buttonTop, buttonWidth, buttonHeight);
            buttonLeft += nextButtonX;
            pgnButtonEnd.setBounds  (buttonLeft, buttonTop, buttonWidth, buttonHeight);

            buttonWidth = 100;
            buttonLeft  = rightSpaceLeft + (rightSpaceWidth / 2) - (buttonWidth / 2);
            buttonTop  += nextButtonY;
            preferencesButton.setBounds(buttonLeft, buttonTop, buttonWidth, buttonHeight);

            // set the size and location of the RAV list
            // the verticle location was set in drawBottonSpace

            int X = rightSpaceLeft + (rightSpaceWidth / 2) - (RAVList.getSize().width / 2);
            int Y = buttonTop + buttonHeight + 8;
            RAVList.setLocation(X, Y);

            if (bPuzzleMode)
            {
                buttonTop  += nextButtonY;
                btnShowAnswer.setBounds(buttonLeft, buttonTop, buttonWidth, buttonHeight);
            }
            else
            {
                btnShowAnswer.setVisible(false);
            }
        }

        // do we need to display who is on move - as in puzzle mode?

        if (bShowSideOnMove)
        {
            g.setFont(new Font("Dialog", Font.BOLD, 12));
            fm = g.getFontMetrics();
            g.setColor(squareLightColor);

            String strOnMove = bWhiteToMove ? "White to Move" : "Black to Move";

            int X = rightSpaceLeft + (rightSpaceWidth / 2) - (fm.stringWidth(strOnMove) / 2);
            int Y = btnShowAnswer.isVisible() ? btnShowAnswer.getLocation().y + fm.getHeight() + buttonHeight + 2
                                              : preferencesButton.getLocation().y + fm.getHeight() + buttonHeight + 2;

            g.drawString(strOnMove, X, Y);
        }
    }

    private void drawPiece(Graphics g, char piece, int x, int y)
    {
        switch (piece)
        {
            case 'P':

                g.drawImage(wPawnImage, x, y, this);
                break;

            case 'N':

                g.drawImage(wKnightImage, x, y, this);
                break;

            case 'B':

                g.drawImage(wBishopImage, x, y, this);
                break;

            case 'R':

                g.drawImage(wRookImage, x, y, this);
                break;

            case 'Q':

                g.drawImage(wQueenImage, x, y, this);
                break;

            case 'K':

                g.drawImage(wKingImage, x, y, this);
                break;

            case 'p':

                g.drawImage(bPawnImage, x, y, this);
                break;

            case 'n':

                g.drawImage(bKnightImage, x, y, this);
                break;

            case 'b':

                g.drawImage(bBishopImage, x, y, this);
                break;

            case 'r':

                g.drawImage(bRookImage, x, y, this);
                break;

            case 'q':

                g.drawImage(bQueenImage, x, y, this);
                break;

            case 'k':

                g.drawImage(bKingImage, x, y, this);
                break;

            default:

                break;
        }
    }

    /*
     *    This draws a "MessageBox" with the given foreground
     *    and background.  It will be draw so that the given x, y
     *    coordinate is the center of the box.  strMessage can be
     *    several lines separated be the newline character '\n'.
     *    The box is auto-sized around strMessage.
     **/

    private void drawMessageBox(Graphics g,
                                String strMessage,
                                Color foreground,
                                Color background,
                                int x,
                                int y)
    {
        // first, get the lines in our error message as 'tokens'

        StringTokenizer st = new StringTokenizer(strMessage, "\n");

        // set the font

        g.setFont(new Font("Dialog", Font.PLAIN, 12));
        FontMetrics fm = g.getFontMetrics();

        // get the dimension of the message box

        int iBoxHeight = fm.getHeight() * (st.countTokens() + 2);
        int iBoxWidth = 0;

        while (st.hasMoreTokens())
        {
            // find the longest string

            String strLine = st.nextToken();

            if (fm.stringWidth(strLine) > iBoxWidth - 40)
            {
                iBoxWidth = fm.stringWidth(strLine) + 40;
            }
        }

        // make the given x, y point the center of the message box

        x -= iBoxWidth / 2;
        y -= iBoxHeight / 2;

        // draw the box background with a black border

        g.setColor(background);
        g.fillRect(x,
                   y,
                   iBoxWidth,
                   iBoxHeight);

        g.setColor(Color.black);
        g.drawRect(x,
                   y,
                   iBoxWidth,
                   iBoxHeight);

        // now draw the message text

        int iTextX;
        int iTextY = y + fm.getAscent();
        st = new StringTokenizer(strMessage, "\n");

        g.setColor(foreground);
        while (st.hasMoreTokens())
        {
            String strLine = st.nextToken();

            iTextX  = x + ((iBoxWidth / 2) - (fm.stringWidth(strLine) / 2));
            iTextY += fm.getHeight();

            g.drawString(strLine, iTextX, iTextY);
        }
    }

    /*
     *    This draws an error "MessageBox" with a red background and
     *    a black forground.  It will be draw so that the given x, y
     *    coordinate is the center of the box.  strMessage can be
     *    several lines separated be the newline character '\n'.  The box
     *    is automatically sized around strMessage.
     **/

    private void drawErrorMessage(Graphics g, String strMessage, int x, int y)
    {

        drawMessageBox(g,
                       strMessage,
                       Color.black,
                       Color.red,
                       x,
                       y);
    }

  public void mouseMoved(MouseEvent e)
    {
    }

    public void mouseDragged(MouseEvent e)
    {
        // have we picked up a piece? return if not

        if (squarePressed == -1)
        {
            return;
        }

        // make sure we draw source square empty

        scoreKeeper.getCurrentPosition()[squarePressed] = '-';

        // calculated the curent x, y of draggedPiece

        draggedPieceX = e.getX() - draggedPieceToMouseX;
        draggedPieceY = e.getY() - draggedPieceToMouseY;

        // repaint!

        repaint();
    }

    public void mouseClicked(MouseEvent e)
    {
    }

    public void mouseEntered(MouseEvent e)
    {
    }

    public void mouseExited(MouseEvent e)
    {
    }

    public void mousePressed(MouseEvent e)
    {
        // first, are we clicking on a scoresheet move?

        int moveIndex = xyToScoreSheetMoveBox(e.getX(), e.getY());
        if (moveIndex != -1)
        {
            scoreKeeper.gotoPosition(moveIndex);
            repaint();
            return;
        }

        // if dragging is off, just return

        if (!isDraggingOn)
        {
            return;
        }

        // make sure we're not already dragging a piece around
        // it is possible to press another button during dragging

        if (squarePressed != -1)
        {
            return;
        }

        squarePressed = xyToSquareNumber(e.getX(), e.getY());

        // are we on a valid chess square?

        if (squarePressed == -1)
        {
            return;
        }

        // are we pressing an empty square?

        if (scoreKeeper.getCurrentPosition()[squarePressed] == '-')
        {
            squarePressed = -1;
            return;
        }

        // this is the piece we'll be dragging

        draggedPiece  = scoreKeeper.getCurrentPosition()[squarePressed];

        // calculate the the x, y coordinate of the dragged piece

        int file, rank;
        if (whiteFromBottom)
        {
            file = (squarePressed + 8) % 8;
            rank = squarePressed  / 8;
        }
        else
        {
            file = (63 - squarePressed + 8) % 8;
            rank = (63 - squarePressed) / 8;
        }

        int pieceWidth    = wPawnImage.getWidth(this);
        int pieceHeight   = wPawnImage.getHeight(this);
        int pieceOffsetX  = (squareWidth / 2) - (pieceWidth / 2);
        int pieceOffsetY  = squareHeight - pieceHeight;

        draggedPieceX = leftMargin + (file * squareWidth+ pieceOffsetX;
        draggedPieceY = topMargin  + (rank * squareHeight) + pieceOffsetY;

        // now, calculate to relative distance from the mouse's
        // current x, y to draggedPiece's current x,y

        draggedPieceToMouseX = e.getX() - draggedPieceX;
        draggedPieceToMouseY = e.getY() - draggedPieceY;
    }

    public void mouseReleased(MouseEvent e)
    {
         // TEMPORARY: just return the piece

         if (squarePressed != -1)
         {
             scoreKeeper.getCurrentPosition()[squarePressed] = draggedPiece;
             squarePressed = -1;
         }

         repaint();
    }

    /**
     *     This method converts an x, y coordinate
     *     (from a mouse most likely) to a square number.  This
     *     number can then be used to locate the piece a user is
     *     attempting to pick up (or the square to drop a piece off)
     *     by indexing into currentPosition.
     */

    private int xyToSquareNumber(int x, int y)
    {
        // are we on a valid chess square?

        if (x < leftMargin || x > leftMargin + (squareWidth  * 8) ||
            y < topMargin  || y > topMargin  + (squareHeight * 8))
        {
            return -1;
        }

        // now calulate the square number

        int squaresToTheRight  = (x - leftMargin) / squareWidth;
        int squaresToTheBottom = (y - topMargin ) / squareHeight;
        int squareNum = squaresToTheBottom * 8 + squaresToTheRight;

        return whiteFromBottom ? squareNum : 63 - squareNum;
    }

    /**
     *    This method finds the point location (left, top) of
     *    the given square number.
     */

    private Point squareNumberToPoint(int square)
    {
        int workSquare = !whiteFromBottom ? Math.abs(square - 63) : square;
        int file       = workSquare % 8;
        int rank       = workSquare / 8;
        int x          = leftMargin + (file * squareWidth);
        int y          = topMargin  + (rank * squareHeight);

        return new Point(x, y);
    }

    /**
     *     This method converts an x, y coordinate
     *     (from a mouse most likely) to a scoresheet move box.  This
     *     number will the correspond to that move's index in the
     *     scoreKeeper.
     */

    private int xyToScoreSheetMoveBox(int x, int y)
    {
        // are we on a valid scoresheet move box?

        if (x < scoreSheetLeft     || x > scoreSheetRight ||
            y < scoreSheetMovesTop || y > scoreSheetMovesTop + (moveLines * moveBoxHeight))
        {
            return -1;
        }

        // now calulate the move box number (or index)

        int boxIndex = (((y - scoreSheetMovesTop) / moveBoxHeight) * 2) + 1;

        if (x > blacksRightColLeft)
        {
            boxIndex += (moveLines * 2) + 1;
        }
        else if (x > scoreSheetMiddle)
        {
            boxIndex += moveLines * 2;
        }
        else if (x > blacksLeftColLeft)
        {
            boxIndex++;
        }

        // now figure out what page where currently on and adjuct
        // the index accordingly

        int movesPerPage = moveLines * 4;
        int page = (scoreKeeper.getCurrentMoveIndex() - 1) / movesPerPage;

        boxIndex += (page * movesPerPage);

    // did black have the first move on a FEN position?
    if (scoreKeeper.blackHadFirstMove())
    {
      boxIndex--;
    }

        // make sure we're not beyond the last move

        if (boxIndex > scoreKeeper.getTotalMoves())
        {
            return -1;
        }

        return boxIndex;
    }

    private void AnimateNextMove()
    {
        // wait for the previous animation to finish if the user
        // is wildly clicking the nextMove button

        if (animationThread == null || !animationThread.isAlive())
        {
            ChessMove cm = scoreKeeper.getCurrentChessMove();

            isAnimation          = true;
            animatedSourceSquare = cm.getSourceSquare();
            animatedDestSquare   = cm.getDestSquare();
            animationPosition    = (char[])scoreKeeper.getCurrentPosition().clone();
            animatedPiece        = animationPosition[animatedSourceSquare];
            animationPosition[animatedSourceSquare] = '-';

            scoreKeeper.gotoPosition(scoreKeeper.getCurrentMoveIndex() + 1);

            animationThread = new Thread(this, "Animation");
            animationThread.start();
        }
    }

    // this processes our applets buttons

    public void actionPerformed(ActionEvent e)
    {
        Object object = e.getSource();

        if (object == pgnButtonNext &&
            scoreKeeper.getCurrentMoveIndex() < scoreKeeper.getTotalMoves() - 1)
        {
          broadcastMove();
            if (bAnimateMoves)
            {
                AnimateNextMove();
            }
            else
            {
                scoreKeeper.gotoPosition(scoreKeeper.getCurrentMoveIndex() + 1);
                repaint();
            }
            storeCurrentPosition();
        }
        else if (object == pgnButtonPrev &&
                 scoreKeeper.getCurrentMoveIndex() > 0)

        {
            scoreKeeper.gotoPosition(scoreKeeper.getCurrentMoveIndex() - 1);
            repaint();
            storeCurrentPosition();
        }
        else if (object == pgnButtonStart)
        {
            scoreKeeper.gotoPosition(0);
            repaint();
            storeCurrentPosition();
        }
        else if (object == pgnButtonEnd)
        {
            if (bPuzzleMode && !bShowAnswer)
            {
                bShowAnswer     = true;
                bShowSideOnMove = false;
                btnShowAnswer.setVisible(false);
            }

            scoreKeeper.gotoPosition(scoreKeeper.getTotalMoves() - 1);
            repaint();
            storeCurrentPosition();
        }
        else if (object == preferencesButton)
        {
            Point p = preferencesButton.getLocation();
            preferencesMenu.show(this, p.x, p.y + 20);
        }
        else if (object == btnShowAnswer)
        {
            bShowAnswer     = true;
            bShowSideOnMove = false;
            btnShowAnswer.setVisible(false);
            repaint();
        }
        else if (object == miFlipBoard)
        {
            whiteFromBottom = whiteFromBottom ? false : true;
            repaint();
        }
        else if (object == miAnimateMoves)
        {
            if (bAnimateMoves)
            {
                bAnimateMoves = false;
                miAnimateMoves.setLabel("Turn Animation On");
            }
            else
            {
                bAnimateMoves = true;
                miAnimateMoves.setLabel("Turn Animation Off");
            }
        }
        else if (object == miShowGamesList)
        {
            if (pgnGameList.isVisible())
            {
                showPGNGameList = false;
                pgnGameList.setVisible(false);
                miShowGamesList.setLabel("Show Games List");
            }
            else
            {
                showPGNGameList = true;
                commentWindow.setVisible(false);
                pgnGameList.setVisible(true);
                miShowGamesList.setLabel("Hide Games List");
            }
        }
    }

    public void itemStateChanged(ItemEvent e)
    {
        Object object = e.getSource();

        if (object == pgnGameList)
        {
      intLastRAVMoveIndex = 10000;
      readPGNGame(pgnGameList.getSelectedIndex(), false);
            repaint();

            if (bPuzzleMode)
            {
                btnShowAnswer.setVisible(true);
                bShowAnswer = false;
                RAVList.setVisible(false);
            }
        }
        else if (object == RAVList)
        {
            scoreKeeper.setLine(RAVList.getSelectedIndex());
            repaint();
        }
    }

    /**
     * Call JavaScript function to broadcast move information.
     */
    public void broadcastMove()
    {
        if (bWidget) {
        ChessMove cm = scoreKeeper.getCurrentChessMove();
       
        // Get a handle to the current HTML document Window
        // object.
          netscape.javascript.JSObject win = JSObject.getWindow(this);
         
          // Call JavaScript function with arguments.
          Object args[] = {cm.getLongAlg()};
          win.call("broadcastMove", args);     
        }
    }
   
    /**
     * Call JavaScript function to store the current game position
     * to the Preferences server.
     */
    public void storeCurrentPosition()
    {
        if (bWidget) {
         
        // Get a handle to the current HTML document Window
        // object.
          netscape.javascript.JSObject win = JSObject.getWindow(this);
         
          // Call JavaScript function with arguments.
          Object args[] = {new Integer(scoreKeeper.getCurrentMoveIndex())};
          win.call("storePosition", args);     
        }
    }
   
    /**
     * Retrieve the current game position from the JavaScript
     * environment.
     *
     * @return int denoting current game position
     */
    public int getCurrentPosition()
    {
      int result = 0;
        if (bWidget) {
         
        // Get a handle to the current HTML document Window
        // object.
          netscape.javascript.JSObject win = JSObject.getWindow(this);
         
          // Get the value directly from the variable (numbers are
          // typically represented as floating point).
          result = new Double(win.getMember("gamePosition").toString()).intValue();
        }
        return result;
    }

    // window listeners

    public void windowActivated(WindowEvent e)
    {
    }
    public void windowDeactivated(WindowEvent e)
    {
    }
    public void windowClosed(WindowEvent e)
    {
    }
    public void windowClosing(WindowEvent e)
    {
        bWindowClosed = true;
    }
    public void windowOpened(WindowEvent e)
    {
    }
    public void windowIconified(WindowEvent e)
    {
    }
    public void windowDeiconified(WindowEvent e)
    {
    }
}

class PgnGameTags implements Cloneable
{
    String event;
    String site;
    String date;
    String round;
    String white;
    String black;
    String whiteRating;
    String blackRating;
    String result;
    String ECOCode;
    String FEN;

    // override Object.clone

    public Object clone()
    {
        try
        {
            return super.clone();
        }
        catch (CloneNotSupportedException e)
        {
            throw new InternalError();
        }
    }
}
TOP

Related Classes of ozone.owf.sample.applet.ChessBoard

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.