Winwheel.js tutorial: #14 Setting the Prize to be won before spinning


In this tutorial I will show you how to set (specify) the prize the user will win (segment which will be indicated when spinning stops) rather than the default of letting the Winwheel JavaScript code pick a random stop angle.


The stopAngle property of the animation allows you to set where the spinning will stop. Also there is a helper function called getRandomForSegment() which will return a random angle inside the specified segment of the wheel. I show you how to use these below.



Why specify the winning prize?


The default operation of the Winwheel code (for spin-to-stop type animations) is to pick a random place around the wheel where the spinning will stop using the JavaScript Math.random() function. As this is done in JavaScript this happens client-side in the user's browser.


If you want to in any way work out what the user should win in advance of the spinning, for example by having a server-side script calculate this, perhaps based off some database records for the currently logged in user, or ensuring that only one user can win a super prize etc, then setting the prize they will win before the spinning using the stopAngle property of the animation is how to do this.


What you should never do, if your wheel involves winning anything of value, is let the Wheel stop randomly and then transmit the prize the user has won from their client-side JavaScript to a back-end system where what was won is recorded. This is because transmitting the prize won back typically involves an AJAX call to a URL with parameters and hackers can easily figure this out and then send parameters to that url saying they have won the top prize, or any of the other prizes they desire.


First working out in the back-end what prize the user will win, keeping a record of it, and then using a Winning wheel to simply display the result of the calculation is the more secure thing to do.


Of course if you are using the winning wheel code to create something that does not involve financial risk, there is nothing to stop you writing a function in JavaScript on the page which figures out the stopAngle. I will start an example of that...



Calculate stopAngle before spinning from JS on the page


In this example before each spin of the wheel I call a JavaScript function on the page to calculate and set the stopAngle of the wheel. In this case the game is rigged so the user can only win Prize 3.


<script>
    let theWheel = new Winwheel({
        'numSegments'    : 8,
        'outerRadius'    : 170,
        'segments'       :
        [
           {'fillStyle' : '#eae56f', 'text' : 'Prize 1'},
           {'fillStyle' : '#89f26e', 'text' : 'Prize 2'},
           {'fillStyle' : '#7de6ef', 'text' : 'Prize 3'},
           {'fillStyle' : '#e7706f', 'text' : 'Prize 4'},
           {'fillStyle' : '#eae56f', 'text' : 'Prize 5'},
           {'fillStyle' : '#89f26e', 'text' : 'Prize 6'},
           {'fillStyle' : '#7de6ef', 'text' : 'Prize 7'},
           {'fillStyle' : '#e7706f', 'text' : 'Prize 8'}
        ],
        'animation' :
        {
            'type'          : 'spinToStop',
            'duration'      : 5,
            'spins'         : 8,
            'callbackAfter' : 'drawTriangle()'
        }
    });

    // Function with formula to work out stopAngle before spinning animation.
    // Called from Click of the Spin button.
    function calculatePrize()
    {
        // This formula always makes the wheel stop somewhere inside prize 3 at least
        // 1 degree away from the start and end edges of the segment.
        let stopAt = (91 + Math.floor((Math.random() * 43)))

        // Important thing is to set the stopAngle of the animation before stating the spin.
        theWheel.animation.stopAngle = stopAt;

        // May as well start the spin from here.
        theWheel.startAnimation();
    }

    // Usual pointer drawing code.
    drawTriangle();

    function drawTriangle()
    {
        // Get the canvas context the wheel uses.
        let ctx = theWheel.ctx;

        ctx.strokeStyle = 'navy';     // Set line colour.
        ctx.fillStyle   = 'aqua';     // Set fill colour.
        ctx.lineWidth   = 2;
        ctx.beginPath();              // Begin path.
        ctx.moveTo(170, 5);           // Move to initial position.
        ctx.lineTo(230, 5);           // Draw lines to make the shape.
        ctx.lineTo(200, 40);
        ctx.lineTo(171, 5);
        ctx.stroke();                 // Complete the path by stroking (draw lines).
        ctx.fill();                   // Then fill.
    }

</script>


Canvas not supported    Reset


Calling server-side script to calculate prize won


In this example before each spin of the wheel I use AJAX to make a call to a server-side PHP script which calculates and returns the number of the prize the user is to win. I then use the getRandomForSegment() method to set the stopAngle of the animation to a random angle within that segment.


<script>
    let theWheel = new Winwheel({
        'numSegments' : 8,
        'outerRadius' : 170,
        'segments'    :
        [
           {'fillStyle' : '#eae56f', 'text' : 'Prize 1'},
           {'fillStyle' : '#89f26e', 'text' : 'Prize 2'},
           {'fillStyle' : '#7de6ef', 'text' : 'Prize 3'},
           {'fillStyle' : '#e7706f', 'text' : 'Prize 4'},
           {'fillStyle' : '#eae56f', 'text' : 'Prize 5'},
           {'fillStyle' : '#89f26e', 'text' : 'Prize 6'},
           {'fillStyle' : '#7de6ef', 'text' : 'Prize 7'},
           {'fillStyle' : '#e7706f', 'text' : 'Prize 8'}
        ],
        'animation' :
        {
            'type'          : 'spinToStop',
            'duration'      : 5,
            'spins'         : 8,
            'callbackAfter' : 'drawTriangle()'
        }
    });

    // In this example I use raw Javascript to do the AJAX, but if you have jQuery included
    // in your website so might want to use its AJAX features as its a bit less code to write.
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = ajaxStateChange;

    // Function called on click of spin button.
    function calculatePrizeOnServer()
    {
        // Make get request to the server-side script.
        xhr.open('GET',"calculate_prize.php", true);
        xhr.send('');
    }

    // Called when state of the HTTP request changes.
    function ajaxStateChange()
    {
        if (xhr.readyState < 4) {
            return;
        }

        if (xhr.status !== 200) {
            return;
        }

        // The request has completed.
        if (xhr.readyState === 4) {
            let segmentNumber = xhr.responseText;   // The segment number should be in response.

            if (segmentNumber) {
                // Get random angle inside specified segment of the wheel.
                let stopAt = theWheel.getRandomForSegment(segmentNumber);

                // Important thing is to set the stopAngle of the animation before stating the spin.
                theWheel.animation.stopAngle = stopAt;

                // Start the spin animation here.
                theWheel.startAnimation();
            }
        }
    }

    // Usual pointer drawing code.
    drawTriangle();

    function drawTriangle()
    {
        // Get the canvas context the wheel uses.
        let ctx = theWheel.ctx;

        ctx.strokeStyle = 'navy';  // Set line colour.
        ctx.fillStyle   = 'aqua';  // Set fill colour.
        ctx.lineWidth   = 2;
        ctx.beginPath();           // Begin path.
        ctx.moveTo(170, 5);        // Move to initial position.
        ctx.lineTo(230, 5);        // Draw lines to make the shape.
        ctx.lineTo(200, 40);
        ctx.lineTo(171, 5);
        ctx.stroke();              // Complete the path by stroking (draw lines).
        ctx.fill();                // Then fill.
    }
</script>


Canvas not supported    Reset


Known issue - segments share some angles


One thing that you should keep in mind when specifying the stopAngle, is that in order to draw a complete circle, the endAngle of a segment has the same startAngle as the next segment in the wheel - i.e. they share it, so you need to be careful when specifying the stop angle that it does not cause a different prize to be awarded than you intended.


For example if the wheel had 4 segments the segment for prize 1 has a start angle of 0 degrees and and end angle of 90 degrees, segment 2 has a start angle of 90 degrees and ends at 180 degrees. So if you specified a stop angle of 90 degrees thinking that prize 2 would be won, that is incorrect prize 1 would be won because the getIndicatedPrize() and getIndicatedPrizeNumber() functions loop through the segments in the wheel in order to work out what is pointed to.


Therefore to be on the safe side it is best to specify a stopAngle at least 1 degree inside the start and end of the segment. The getRandomForSegment() function does this. To continue the example above it would pick a random stop angle between 91 and 179 degrees for prize 2 in a 4 segment wheel.


Note: If in your calculations you need to know the starting and ending angle of a segment in degrees, this can be got from the winwheel.segments[x].startAngle and winwheel.segments[x].endAngle properties.


Previous:
< #13 Getting the segment (prize) pointed to
Next:
#15: Getting the segment clicked >