/*******************************************************************************
*                                                                              *
*  File:        Squash.js                               Revision:  1.0         *
*                                                                              *
*  Purpose:     basic routines for the "Squash" demo game                      *
*                                                                              *
*  Creation:    25.06.1998                     Last Modification:  08.05.2002  *
*                                                                              *
*  Platform:    IBM-compatible PC running Windows 98SE                         *
*                                                                              *
*  Environment: JavaScript 1.3                                                 *
*                                                                              *
*  Author:      Andreas Rozek           Phone:  ++49 (711) 6770682             *
*               Kirschblütenweg 15      Fax:    -                              *
*             D-70569 Stuttgart         EMail:  Andreas.Rozek@T-Online.De      *
*               Germany                                                        *
*                                                                              *
*  URL:         http://www.Andreas-Rozek.de/                                   *
*                                                                              *
*  Copyright:   this software is published under the  "GNU Lesser General Pub- *
*               lic  License"  (see  "http://www.fsf.org/copyleft/lesser.html" *
*               for additional information)                                    *
*                                                                              *
*  Comments:    (none)                                                         *
*                                                                              *
*******************************************************************************/

/**** table of supported colors (each color requires a corresponding pixel image) ****/

  function newImage (ImageURL) {
    var Result = new Image(_PixelSize,_PixelSize);
      Result.src = ImageURL;
    return Result;
  };

  var Black   = newImage("./BlackPixel.gif");
  var Gray    = newImage("./GrayPixel.gif");
  var Maroon  = newImage("./MaroonPixel.gif");
  var Red     = newImage("./RedPixel.gif");
  var Green   = newImage("./GreenPixel.gif");
  var Lime    = newImage("./LimePixel.gif");
  var Olive   = newImage("./OlivePixel.gif");
  var Yellow  = newImage("./YellowPixel.gif");
  var Navy    = newImage("./NavyPixel.gif");
  var Blue    = newImage("./BluePixel.gif");
  var Purple  = newImage("./PurplePixel.gif");
  var Fuchsia = newImage("./FuchsiaPixel.gif");
  var Teal    = newImage("./TealPixel.gif");
  var Aqua    = newImage("./AquaPixel.gif");
  var Silver  = newImage("./SilverPixel.gif");
  var White   = newImage("./WhitePixel.gif");

  var _ColorTable = new Object();
    _ColorTable["black"]   = Black;   _ColorTable[0]  = Black;
    _ColorTable["gray"]    = Gray;    _ColorTable[1]  = Gray;
    _ColorTable["maroon"]  = Maroon;  _ColorTable[2]  = Maroon;
    _ColorTable["red"]     = Red;     _ColorTable[3]  = Red;
    _ColorTable["green"]   = Green;   _ColorTable[4]  = Green;
    _ColorTable["lime"]    = Lime;    _ColorTable[5]  = Lime;
    _ColorTable["olive"]   = Olive;   _ColorTable[6]  = Olive;
    _ColorTable["yellow"]  = Yellow;  _ColorTable[7]  = Yellow;
    _ColorTable["navy"]    = Navy;    _ColorTable[8]  = Navy;
    _ColorTable["blue"]    = Blue;    _ColorTable[9]  = Blue;
    _ColorTable["purple"]  = Purple;  _ColorTable[10] = Purple;
    _ColorTable["fuchsia"] = Fuchsia; _ColorTable[11] = Fuchsia;
    _ColorTable["teal"]    = Teal;    _ColorTable[12] = Teal;
    _ColorTable["aqua"]    = Aqua;    _ColorTable[13] = Aqua;
    _ColorTable["silver"]  = Silver;  _ColorTable[14] = Silver;
    _ColorTable["white"]   = White;   _ColorTable[15] = White;

/**** global constants ****/

  var _Prefix    = "Squash";                                  // image id prefix
  var _PixelSize = 5;                // defines the "natural" size of each pixel

  var _DisplayWidth  = 60;                              // display width [pixel]
  var _DisplayHeight = 45;                             // display height [pixel]

  var _Background = Black;                                   // background color
  var _Border     = Green;                                       // border color
  var _Ball       = Yellow;                                        // ball color
  var _Racket     = Purple;                                      // racket color

  var _Player   = "";                                   // name of actual player
  var _Skill    = 0;                             // skill level of actual player
  var _HitCount = 0;                               // hit count of actual player

  var _TopName = new Array("","","","");            // names of top game players
  var _TopHits = new Array(0, 0, 0, 0);        // hit counts of top game players

  var _BallSpeed = 1;         // max. ball displacement per "clock tick" [pixel]
  var _RacketWidth = 8;                               // width of racket [pixel]
  var _RacketSpeed = 1;     // max. racket displacement per button press [pixel]

  var _BallX  = 0;                                   // horizontal ball position
  var _BallY  = 0;                                     // vertical ball position

  var _BallDX = 0;                                      // horizontal ball speed
  var _BallDY = 0;                                        // vertical ball speed

  var _RacketX  = 0;                             // (horizontal) racket position
  var _RacketDX = 0;                              // (horizontal) racket "speed"

  var _SquashForm = null;// stores a reference to the form with the squash panel
  var _BallThread = null;     // stores a reference to the ball animation thread

/*******************************************************************************
*                                                                              *
* _clearBall                            clears the ball at its actual position *
*                                                                              *
*******************************************************************************/

  function _clearBall () {
    _setPixel(_BallX,_BallY, _Background);
  };

/*******************************************************************************
*                                                                              *
* _clearRacket                 clears the entire racket at its actual position *
*                                                                              *
*******************************************************************************/

  function _clearRacket () {
    for (var i = 0; i < _RacketWidth; i++) {
      _setPixel(_RacketX+i,_DisplayHeight-1, _Background);
    };
  };

/*******************************************************************************
*                                                                              *
* _drawBall                              draws the ball at its actual position *
*                                                                              *
*******************************************************************************/

  function _drawBall () {
    _setPixel(_BallX,_BallY, _Ball);
  };

/*******************************************************************************
*                                                                              *
* _drawRacket                   draws the entire racket at its actual position *
*                                                                              *
*******************************************************************************/

  function _drawRacket () {
    for (var i = 0; i < _RacketWidth; i++) {
      _setPixel(_RacketX+i,_DisplayHeight-1, _Racket);
    };
  };

/*******************************************************************************
*                                                                              *
* _moveLeft                               moves the racket display to the left *
*                                                                              *
*******************************************************************************/

  function _moveLeft (dX) {
    for (var i = 0; i < dX; i++) {
      _setPixel(_RacketX+i,             _DisplayHeight-1, _Racket);
      _setPixel(_RacketX+_RacketWidth+i,_DisplayHeight-1, _Background);
    };
  };

/*******************************************************************************
*                                                                              *
* _moveRight                             moves the racket display to the right *
*                                                                              *
*******************************************************************************/

  function _moveRight (dX) {
    for (var i = 1; i <= dX; i++) {       // these bounds save some subtractions
      _setPixel(_RacketX-i,             _DisplayHeight-1, _Background);
      _setPixel(_RacketX+_RacketWidth-i,_DisplayHeight-1, _Racket);
    };
  };

/*******************************************************************************
*                                                                              *
* _proceed                         calculates and shows the next ball position *
*                                                                              *
*******************************************************************************/

  function _proceed () {
    _clearBall();
    if (_BallThread == null) return;                     // animation has stopped

    var newBallY = Math.round(_BallY + _BallDY);
      if (newBallY < 1) {                           // reflection at upper border
        newBallY = 1-newBallY;
        _BallDY = -_BallDY;
      };

      if (newBallY > _DisplayHeight-2) {         // ball at or below racket level
        var newBallX = _BallX + _BallDX*(_DisplayHeight-1-_BallY)/_BallDY;
        if ((newBallX >= _RacketX) && (newBallX <= _RacketX+_RacketWidth-1)) {
          newBallY = 2*(_DisplayHeight-2)-newBallY;
          _BallDY = -_BallDY;

          _BallDX += _RacketDX;          // racket momentum effects ball movement

          _HitCount++;
          _updateHitCount();
        } else {                                   // ball has left the playfield
          stopGame();                                              // "game over"
          return;
        };
      };
    _BallY = newBallY;

    var newBallX = Math.round(_BallX + _BallDX);
      if (newBallX < 1)               {newBallX = 1-newBallX;                   _BallDX = -_BallDX};
      if (newBallX > _DisplayWidth-2) {newBallX = 2*(_DisplayWidth-2)-newBallX; _BallDX = -_BallDX};
    _BallX = newBallX;

    _drawBall();

  /**** slow down the racket ****/

    if (_RacketDX < 0) _RacketDX++;
    if (_RacketDX > 0) _RacketDX--;
  };

/*******************************************************************************
*                                                                              *
* _setPixel                              sets the given pixel to a given color *
*                                                                              *
*******************************************************************************/

  function _setPixel (x,y, Color) {                      // no parameter checks!
    try {
      x++; y++;                  // really weird: JavaScript may produce "-0"!!!
      document.images[_Prefix+x+"_"+y].src = Color.src;
    } catch (Exception) {
      document.writeln("manipulation of Pixel ",x,",",y," failed!<br>");
    };
  };

/*******************************************************************************
*                                                                              *
* _updateHitCount                                updates the hit count display *
*                                                                              *
*******************************************************************************/

  function _updateHitCount () {
    SquashForm.elements["Hits"].value = _HitCount;
  };

/*******************************************************************************
*                                                                              *
* createDisplay                              sets up the "Squash" display area *
*                                                                              *
*******************************************************************************/

  function createDisplay () {

  /**** construct cell template ****/

    var CellTemplate =
      "<img name=\"" + _Prefix + "[_]\" src=\"" + _Background.src + "\" " +
      "width=" + _PixelSize + " height=" + _PixelSize + ">";

  /**** construct row template ****/

    var RowTemplate = "  <tr><td nowrap height=" + _PixelSize + ">";
      for (var Col = 1; Col <= _DisplayWidth; Col++) {
        RowTemplate += CellTemplate.replace(/\[/,Col);
      };
    RowTemplate += "<\/td><\/tr>";

  /**** construct display table ****/

    document.writeln("<table border=0 cellspacing=0 cellpadding=0 width=" + (_DisplayWidth*_PixelSize) + ">");
    document.writeln("  <colgroup span=1><\/colgroup>");
      for (var Row = 1; Row <= _DisplayHeight; Row++) {
        document.writeln(RowTemplate.replace(/\]/g,Row));
      };
    document.writeln("<\/table>");

  /**** now draw arena borders ****/

    for (var i = 0; i < _DisplayWidth; i++) {
      _setPixel(i,0, _Border);
    };

    for (var i = 1; i < _DisplayHeight; i++) {
      _setPixel(0,i,               _Border);
      _setPixel(_DisplayWidth-1,i, _Border);
    };
  };

/*******************************************************************************
*                                                                              *
* max/min                         yield the larger/smaller of two given values *
*                                                                              *
*******************************************************************************/

  function max (a,b) {
    return a > b ? a : b;
  };

  function min (a,b) {
    return a < b ? a : b;
  };

/*******************************************************************************
*                                                                              *
* moveLeft                               moves the racket one unit to the left *
*                                                                              *
*******************************************************************************/

  function moveLeft () {
    _RacketDX = -min(_RacketX-1,_RacketSpeed);         // calculate displacement
    _RacketX += _RacketDX;                             // update racket position
    if (_BallThread != null) _moveLeft(-_RacketDX);     // update racket display
  };

/*******************************************************************************
*                                                                              *
* moveRight                             moves the racket one unit to the right *
*                                                                              *
*******************************************************************************/

  function moveRight () {
    _RacketDX = min(_DisplayWidth-1-_RacketWidth-_RacketX,_RacketSpeed);
    _RacketX += _RacketDX;                             // update racket position
    if (_BallThread != null) _moveRight(_RacketDX);     // update racket display
  };

/*******************************************************************************
*                                                                              *
* setSkill                                              sets a new skill level *
*                                                                              *
*******************************************************************************/

  function setSkill (Level) {
    _Skill = Level;                             // doesn't affect a running game
  };

/*******************************************************************************
*                                                                              *
* startGame                                                  starts a new game *
*                                                                              *
*******************************************************************************/

  function startGame () {
    if (_BallThread == null) {
      _SquashForm = document.forms["SquashForm"];

    /**** determine actual gaming parameters ****/

      switch (parseInt(_Skill)) {
        case 0:                                                        // Novice
          _BallSpeed = 2; _RacketWidth = 8; _RacketSpeed = 3; break;
        case 1:                                                       // Amateur
          _BallSpeed = 3; _RacketWidth = 7; _RacketSpeed = 2; break;
        case 2:                                                  // Professional
          _BallSpeed = 4; _RacketWidth = 6; _RacketSpeed = 2; break;
      };

    /**** initialize the game state ****/

      _RacketX  = 1+Math.floor((_DisplayWidth-2-_RacketWidth)*Math.random());
      _RacketDX = 0;

      _BallX  = _RacketX+Math.floor(_RacketWidth/2);
      _BallY  = _DisplayHeight-2;

      _BallDX = _BallSpeed * 2*(Math.random()-0.5);
        while (_BallDX == 0) _BallDX = _BallSpeed * 2*(Math.random()-0.5);
      _BallDY = -_BallSpeed;

      _HitCount = 0;

    /**** update display ****/

      _drawRacket();
      _drawBall();

    /**** now start the ball ****/

      _BallThread = window.setInterval("_proceed()",100);
    };
  };

/*******************************************************************************
*                                                                              *
* stopGame                                               stops the actual game *
*                                                                              *
*******************************************************************************/

  function stopGame () {
    if (_BallThread != null) {
      window.clearInterval(_BallThread); _BallThread = null;
      _clearBall();                                       // just to be complete
      _clearRacket();                                                    // dto.

    /**** find positon of actual player within the ranking list ****/

      var Position = 0;
      while ((Position <= 3) && (_HitCount <= _TopHits[Position])) Position++;
      if (Position <= 3) {                  // uuh - seems to be a "top player"!

      /**** determine player's name ****/

        _Player = _SquashForm.elements["Player"].value.replace(/^ */,"").replace(/ *$/,"");
        if (_Player == "") _Player = "(unknown)";

      /**** make room for the new player ****/

        for (var i = 2; i >= Position; i--) {
          _TopName[i+1] = _TopName[i];
          _TopHits[i+1] = _TopHits[i];
        };

      /**** insert actual player into ranking list ****/

        _TopName[Position] = _Player;
        _TopHits[Position] = _HitCount;

      /**** update ranking list display ****/

        SquashForm.elements["Top1_Player"].value = _TopName[0];
        SquashForm.elements["Top1_Hits"].value   = (_TopHits[0] == 0 ? "" : _TopHits[0]);

        SquashForm.elements["Top2_Player"].value = _TopName[1];
        SquashForm.elements["Top2_Hits"].value   = (_TopHits[1] == 0 ? "" : _TopHits[1]);

        SquashForm.elements["Top3_Player"].value = _TopName[2];
        SquashForm.elements["Top3_Hits"].value   = (_TopHits[2] == 0 ? "" : _TopHits[2]);

        SquashForm.elements["Top4_Player"].value = _TopName[3];
        SquashForm.elements["Top4_Hits"].value   = (_TopHits[3] == 0 ? "" : _TopHits[3]);
      };
    };
  };

