A programmers jigsaw: Bowling score simulation

Yesterday I found a job posting online that had a nice little acceptance task associated in order to be considered for the position. The task itself let you demonstrate many elementary skills while not being huge.

I mainly completed the task because I found it fun, for me it was putting together a jigsaw and testing my brain. It was actually a good way of decoupling from my projects, and it was simple enough to give me a success after some days of struggles. I have learned from this that it can be a good way to regain motivation.

I have chosen to blog about the task and show my proposal and explain how I tackled it. Feel free to comment on my implementation :-) I personally think these tasks are a good reference point for future interviews when they want me to show what I'm capable of and how I work.

I already learned that how I failed to think TDD in this small project. So it made me realize where I need to add more focus.

You can see my blog post on my blog at https://blog.emilmoe.com/bowling-score-simulation/ where I explain all of it.

Expert in Laravel and Vue backed with MySQL databases. Independent developer who does freelance and love to travel. Feel free to drop me a message.

- Emil

Write your comment…

A nice problem to solve!

I did a little different way in which I didn't have to check for 11th or 12th rounds as being special.

https://gist.github.com/sidhantpanda/a5a26c2addfc3887f8ec8c32eca239d8

Reply to this…

Share your programming knowledge and learn from the best developers on Hashnode

Get started

I tried to implement automated tests. Unfortunately I have some errors at calculating strikes where it for some reason only calculates the first shot after a strike. How come I'm not sure. Hope someone can see it :-)

<!doctype html>

<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Bowling Game</title>
    </head>

    <body>
        <div id="app">
            {{ rounds }}
            <br>
            <br>
            {{ points }}
            <br>
            <br>
            {{ score }}
        </div>

        <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.3/vue-resource.js"></script>
        <script>
            /**
             * Compare if two arrays are equal.
             * 
             * @param  Array    array  Array to compare against.
             * @return Boolean
             */
            Array.prototype.equals = function (array) {
                if (!array)
                    return false;

                if (this.length != array.length)
                    return false;

                for (var i = 0, l=this.length; i < l; i++) {
                    if (this[i] instanceof Array && array[i] instanceof Array) {
                        if (!this[i].equals(array[i]))
                            return false;       
                    }
                    else if (this[i] != array[i]) {
                        return false;
                    }
                }

                return true;
            }

            new Vue({
                /**
                 * Application binding.
                 */
                el: '#app',

                /**
                 * Application variables.
                 */
                data: {
                    token: null,
                    points: [],
                    rounds: []
                },

                /**
                 * Start the application
                 *
                 * Initializes the applications, performs remote requests and outputs result to console.
                 */
                mounted() {
                    this.test();

                    this.$http.get('http://37.139.2.74/api/points').then(function(response) {
                        this.token  = response.data.token;
                        this.rounds = this.prepare(response.data.points);
                        this.points = this.calculate(this.rounds);

                        this.$http.post('http://37.139.2.74/api/points', {
                                'token':  this.token,
                                'points': this.score
                            }).then(function(response) {
                                console.log('Is valid: '+ response.data.success);
                            }.bind(this));
                    }.bind(this));
                },

                computed: {
                    /**
                     * Get final computed score array.
                     * Will add each round of points together increasingly to form an array of the scoreboard.
                     * 
                     * @return Array
                     */
                    score: function() {
                        var sum   = 0;
                        var score = []

                        this.forEach(this.points, function(point) {
                            sum += point;
                            score.push(sum);
                        });

                        return score;
                    }
                },

                methods: {
                    /**
                     * Test the implementation
                     * 
                     * @return {[type]} [description]
                     */
                    test() {
                        var cases = [
                            // Strikes
                            [[10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 10]],
                            // Spares
                            [[5, 5], [5, 5], [5, 5], [5, 5], [5, 5], [5, 5], [5, 5], [5, 5], [5, 5], [5, 5], [10]],
                            // Regular, strikes and spares
                            [[3,7],[10,0],[8,2],[8,1],[10,0],[3,4],[7,0],[5,5],[3,2],[2,5]],
                            // Regular and strikes
                            [[3, 2], [4, 3], [10, 0], [10, 0], [10, 0], [5, 4], [10, 0], [10, 0], [10, 0], [4, 5]],
                            // Regular and spares
                            [[3, 7], [3, 4], [5, 2], [7, 3], [7, 3], [3, 4], [6, 4], [3, 4], [3, 6], [6, 2]],
                            // Strikes and spares
                            [[10, 0], [10, 0], [10, 0], [5, 3], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 10]]
                        ];

                        var results = [
                            [30, 60, 90, 120, 150, 180, 210, 240, 270, 300],
                            [15, 30, 45, 60, 75, 90, 105, 120, 135, 155],
                            [20, 40, 58, 67, 84, 91, 98, 111, 116, 123],
                            [5, 12, 42, 67, 86, 95, 125, 149, 168, 177],
                            [13, 20, 27, 44, 57, 64, 77, 84, 93, 101],
                            [30, 55, 73, 81, 111, 141, 171, 201, 231, 261]
                        ];

                        for (var i = 0; i < cases.length; i++) {
                            this.rounds = this.prepare(cases[i]);
                            this.points = this.calculate(this.rounds);
                            console.log('Test case #'+ (i + 1) +': '+ this.score.equals(results[i]));
                        }

                    },

                    /**
                     * Calculate round results.
                     * Will return an array of points per round.
                     * 
                     * @param  Array    rounds  Rounds in game.
                     * @return Array
                     */
                    calculate(rounds) {
                        var points = [];

                        this.forEach(rounds, function(round, index) {
                            if (index > 9) return;

                            points.push(this.getPoints(round, index, rounds, (index + 2)));
                        }.bind(this));

                        return points;
                    },

                    /**
                     * Determine if round is strike.
                     * 
                     * @param  Array    round   Round to examine.
                     * @return Boolean
                     */
                    isStrike: function(round) {
                        return round[0] === 10;
                    },

                    /**
                     * Determine if round is spare.
                     * 
                     * @param  Array    round   Round to examine.
                     * @return Boolean
                     */
                    isSpare: function(round) {
                        return round[0] < 10 && (round[0] + round[1]) === 10;
                    },

                    /**
                     * Determines if index is last entry of array.
                     * 
                     * @param  Number   index    Entry index.
                     * @param  Array    array    Array to analyze.
                     * @return Boolean
                     */
                    isLast: function(index, array) {
                        return index + 1 === array.length;
                    },

                    /**
                     * Sum points for round.
                     *
                     * The method will recursively sum up for strikes and spares. The limit is needed for
                     * the method to know, how far ahead in the rounds array it is allowed to sum st rikes.
                     * For default rules in a ten-pin bowling games should 2.
                     * 
                     * @param  Array    round   Round to examine.
                     * @paream Number   index   Current index in rounds array. Should in most cases be 0.
                     * @param  Array    rounds  All rounds in game.
                     * @param  Number   limit   How far in the round array to sum up for strike. Usually (index + 2)
                     * @return Number           Total score for round.
                     */
                    getPoints: function(round, index, rounds, limit) {
                        var score = round[0] + round[1];

                        if (this.isStrike(round) && (index < limit && index < 11 && ! this.isLast(index, rounds)))
                            score += this.getPoints(rounds[index + 1], (index + 1), rounds, limit);

                        if (this.isSpare(round) && ! this.isLast(index, rounds))
                            score += rounds[index + 1][0];

                        return score;
                    },

                    /**
                     * If extra 11th round is given, prepare the array for the calculation algorithms.
                     * This will form the eventually extra 11th round from 1 into 2 subsets calcuable
                     * by the algorithm. 
                     * 
                     * @param  Array    rounds  Rounds of game.
                     * @return Array
                     */
                    prepare: function(rounds) {
                        if (rounds.length > 10) {
                            rounds.push([rounds[10][1], 0]);
                            rounds[10][1] = 0;
                        }

                        return rounds;
                    },

                    /**
                     * Map array.
                     * 
                     * @param  Array        array      Array to be mapped.
                     * @param  Function     callback   Callback to perform on each entry.
                     */
                    forEach: function(array, callback) {
                        for (var i = 0; i < array.length; i++)
                            callback(array[i], i);
                    }
                }
            })
        </script>
    </body>
</html>

Reply to this…