Ball.ts 6.83 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
module nurdz.game
{
    /**
     * A range of possible vertical speed values for the tennis ball to move when a new serve is made; either
     * after a score or at the start of the game.
     *
     * The speed will be selected between the first and last array elements, inclusive.
     * @const
     * @type {number[]}
     */
    const BALL_STARTSPEED_Y = [-18, 18];

    /**
     * A range of possible horizontal speed values for the tennis ball to move when a new serve is made;
     * either after a score or at the start of the game.
     *
     * The speed will be selected between the first and last array elements, inclusive.
     *
     * NOTE: The serve is made by the player that was just scored on, and as a result the speed values
     * specified should be positive, with the direction being chosen based on the most recent scoring
     * situation.
     *
     * @const
     * @type {number[]}
     */
    const BALL_STARTSPEED_X = [10, 20];

    /**
     * Specifies how many times the ball has to bounce off of a paddle (either will do) before the horizontal
     * speed will get kicked up to a higher value.
     *
     * This tunes the difficulty of the game by modifying the speed of the ball as the volley continues.
     *
     * @const
     * @type {number}
     */
    const BALL_BOUNCE_TIMES = 4;

    /**
     * Specifies the modification to the horizontal speed of the ball every time the speed is kicked up by an
     * ongoing volley.
     *
     * @const
     * @see BALL_BOUNCE_TIMES
     * @type {number}
     */
    const BALL_BOUNCE_ACCELERATION = 1.25;

    /**
     * The size of the trail that follows the ball. This controls how many previous moves of the ball are
     * displayed on the screen to indicate it's motion path.
     *
     * @type {number}
     */
    const BALL_TRAIL_MAXLEN = 5;

    /**
     * This entity represents the ball in the game.
     */
    export class Ball extends Entity
    {
        /**
         * The images that we use to display the ball.
         */
        private _balls : Array<HTMLImageElement>;

        /**
         * The index of the ball image in the _balls array that we are currently rendering with.
         */
        private _ballImg : number;

        /**
         * The velocity of the ball in the X and Y dimensions
         */
        private _speed : Point;

        /**
         * The number of times the ball has bounces off of a paddle during the current volley (this gets
         * reset when the ball gets reset at the start of a serve).
         *
         * This is used to track when the horizontal speed should be kicked up as the volley continues.
         */
        private _bounces : number;

        /**
         * Construct a new ball that will render on the stage provided.
         *
         * @param stage the stage the ball will be on
         */
        constructor (stage : Stage)
        {
            // Invoke the super. Note that we don't provide any location or dimensions here. These will
            // get set later.
            super ("ball", stage, 0, 0, 0, 0, 1, {});

96 97 98
            // Register preloads for our ball images. The size of our entity is determined by the first
            // ball image, so either the first image here needs to be the largest, or (better) they should
            // all have identical dimensions.
99
            this._balls = [];
100
            this._balls[0] = stage.preloadImage ("ball_white.png", this.setDimensions);
101 102 103 104 105 106 107 108 109 110
            this._balls[1] = stage.preloadImage ("ball_blue.png");
            this._balls[2] = stage.preloadImage ("ball_yellow.png");

            // Create the point that will hold our speed.
            this._speed = new Point (0, 0);

            // Init everything else.
            this.reset ();
        }

111 112 113 114 115 116 117 118 119 120 121 122
        /**
         * This gets invoked when our ball image finishes preloading. We use this in order to determine
         * what our width and height are.
         *
         * @param image the image that will represent us
         */
        private setDimensions = (image : HTMLImageElement) =>
        {
            this._width = image.width;
            this._height = image.height;
        };

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
        /**
         * Reset the ball properties for a serve, which includes the position, speed and so on.
         */
        public reset () : void
        {
            // TODO the original checked to see if the game was won here.

            // Set up a random speed for the ball. We flip the sign on the X speed of the ball relative to
            // what it is currently so that the serve alternates sides when the ball resets.
            this._speed.x = Utils.randomIntInRange (BALL_STARTSPEED_X[0],
                                                    BALL_STARTSPEED_X[1]) * (this._speed.x < 0 ? -1 : 1);
            this._speed.y = Utils.randomIntInRange (BALL_STARTSPEED_Y[0], BALL_STARTSPEED_Y[1]);

            // Revert back to the initial ball image and reset the bounce count.
            this._ballImg = 0;
            this._bounces = 0;

            // Flip the ball direction and then center it on the stage. We center vertically, but
            // horizontally by fourths, with the bias being to the side the ball is serving from.
            this._speed.x *= -1;
            this.setStagePositionXY ((this._speed.x > 0)
                                         ? (this._stage.width / 4)
                                         : (this._stage.width - (this._stage.width / 4)),
                                     this._stage.height / 2);
        }

        /**
         * This is called every frame update (tick tells us how many times this has happened) to allow us
         * to update our position.
         *
         * @param stage the stage that we are on
         * @param tick the current engine tick; this advances one for each frame update
         */
        update (stage : Stage, tick : number) : void
        {
            // Translate the ball position by our speed.
            this._position.translate (this._speed);

            // Check if we should bounce off of the ceiling or floor of the stage.
            if ((this._position.y >= stage.height && this._speed.y > 0) || (this._position.y <= 0 && this._speed.y < 0))
            {
                // TODO play wall bounce sound here
                //sndBounceWall.play ();
                this._speed.y *= -1;
            }
        }

        /**
         * Render the ball at the provided location and using the given renderer.
         *
         * We assume our position is at our center.
         *
         * @param x the x of the center of the ball
         * @param y the y of the center of the ball
         * @param renderer the renderer to blit with
         */
        render (x : number, y : number, renderer : Renderer) : void
        {
            // Blit the current ball image at the given location.
            renderer.blitCentered (this._balls[this._ballImg], x, y);
        }
    }
}