Crux Gaming

General Details

A web app designed to provide a useful tool for a popular mobile game.

The site consists of an interface that mimics the in-game UI, allowing the user to easily search all available units and build a team. Each unit can be equipped, and clicking on a unit allows the user to view detailed information about it. This allows the user to build a team of units that they may or may not have to make ideal teams for events in game quickly.

Technical Details

- Uses a custom PHP script to parse a large JSON file containing the game's data and inserts all relevant data into a SQL database.
- Uses Ajax/jQuery to retrieve JSON data containing information and artwork about the unit from the game's wiki API.
- Uses the Twitter typeahead.js and underscore.js libraries to query the database to produce search suggestions based on the units in the database.



                    // import.php
                    //    Takes the Game data in JSON format and parses out the needed info,
                    //    then inserts into the database.

                    <?php
                        // require the config file.
                        require(__DIR__ . "/../includes/config.php");

                        // check for correct usage
                        if(count($argv) != 2)
                        {
                           exit("Invalid number of arguments. Usage: 'import <path>'\n");
                        }

                        // get the file path from the command line argument
                        $path = $argv[1];

                        // check if file exists
                        if(!file_exists($path))
                        {
                            exit("($path) doesn't exist. Usage: 'import <path>'\n");
                        }

                        // check if file is readable
                        if(!is_readable($path))
                        {
                            exit("($path) is not readable. Usage: 'import <path>'\n");
                        }

                        // read the json file contents
                        $jsondata = file_get_contents($path);

                        // convert json object to php associative array
                        $jdata = json_decode($jsondata, true);

                        // iterate through each unit
                        foreach($jdata as $key => $data)
                        {
                            //print($key .  " " . $data["name"] . "\n");

                            // Arena Type is a int 1-7 based off the ai type
                            $arenaType;
                            if (empty($data["ai"])) $arenaType = "0";
                            else
                            {
                                if($data["ai"][0]["target conditions"] === "random" && $data["ai"][0]["target type"] === "party") // type 1
                                    $arenaType = "1";
                                else if($data["ai"][0]["target conditions"] === "hp_50pr_over" && $data["ai"][0]["target type"] === "enemy") // type 2
                                    $arenaType = "2";
                                else if($data["ai"][0]["target conditions"] === "random" && $data["ai"][0]["target type"] === "enemy") // type 3
                                    $arenaType = "3";
                                else if($data["ai"][0]["target conditions"] === "hp_50pr_under" && $data["ai"][0]["target type"] === "enemy") // type 4
                                    $arenaType = "4";
                                else if($data["ai"][0]["target conditions"] === "hp_50pr_under" && $data["ai"][0]["target type"] === "party") // type 5
                                    $arenaType = "5";
                                else if($data["ai"][0]["target conditions"] === "hp_25pr_under" && $data["ai"][0]["target type"] === "party") // type 6
                                    $arenaType = "6";
                                else if($data["ai"][0]["target conditions"] === "hp_75pr_under" && $data["ai"][0]["target type"] === "party") // type 7
                                    $arenaType = "7";
                            }


                            //insert the needed info into the unit table
                            query("INSERT INTO `unit`(`id`, `name`, `rarity`, `cost`, `element`, `hits`, `lord damage range`, `max bc generated`,
                                `stats`, `imp`, `leader skill`, `extra skill`, `bb`, `sbb`, `ubb`, `ai`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", $key, $data["name"],
                                $data["rarity"], $data["cost"], strtoupper($data["element"]), $data["hits"], $data["lord damage range"], $data["max bc generated"],
                                json_encode($data["stats"]), json_encode($data["imp"]), empty($data["leader skill"]) ? "<NONE>" : json_encode($data["leader skill"]),
                                empty($data["extra skill"]) ? "<NONE>" : json_encode($data["extra skill"]), empty($data["bb"]) ? "<NONE>" : json_encode($data["bb"]),
                                empty($data["sbb"]) ? "<NONE>" : json_encode($data["sbb"]), empty($data["ubb"]) ? "<NONE>" : json_encode($data["ubb"]), $arenaType);
                        }

                    ?>


                    

                        /**
                         * scripts.js
                         *
                         * Global JavaScript.
                         */

                        // Holds the unit data as an object for each of the selected units
                        var Units = Array(6);

                        // Holds the sphere data as an object for each of the selected spheres
                        var Spheres = Array(12);

                        // The typing for each of the selected units, defaults to lord
                        var UnitTypes = ["_lord", "_lord", "_lord", "_lord", "_lord", "_lord"];

                        // The index of the currently selected unit/sphere
                        var SelectedIndex = 0;

                        // an array of the unit info window type buttons
                        var TypeBtns;

                        // an array of hover areas for extra info
                        var HoverAreas;


                        // execute when the DOM is fully loaded
                        $(function () {
                            // disable click and drag
                            $("#Table_01").on("dragstart", function (event) { event.preventDefault(); });

                            // put each unit and sphere div element into the buttons array
                            var buttons = [$("#unit1"), $("#unit2"), $("#unit3"), $("#unit4"), $("#unit5"), $("#unit6"),
                                $("#sphclk1"), $("#sphclk2"), $("#sphclk3"), $("#sphclk4"), $("#sphclk5"), $("#sphclk6"),
                                $("#sphclk7"), $("#sphclk8"), $("#sphclk9"), $("#sphclk10"), $("#sphclk11"), $("#sphclk12")];

                            // call OnButtonClick whenever any unit or sphere div is clicked
                            $.each(buttons, function (index, value) {
                                value.click(function() {
                                    OnButtonClick(index);
                                });
                            });

                            // add click listener functions to the unit info window buttons
                            $("#UExit").click(function() {
                                $("#UInfoWindow").hide("fast");
                            });
                            TypeBtns = [$("#UTypeAClk"), $("#UTypeBClk"), $("#UTypeGClk"), $("#UTypeLClk"), $("#UTypeOClk")];
                            $.each(TypeBtns, function (index, value) {
                                value.click(function() {
                                    $.each(TypeBtns, function (index, value) {
                                        var e = value["selector"].replace("Clk", "");
                                        $(e).hide(0);
                                    });
                                    var e = value["selector"].replace("Clk", "");
                                    $(e).show(0);
                                    UnitTypes[SelectedIndex] = index === 0 ? "anima" : index === 1 ? "breaker" : index === 2 ? "guardian" : index === 3 ? "_lord" : "oracle";
                                    UnitInfoBox();
                                });
                            });

                            // Add the areas to register hover events
                            // HoverAreas = [$("#UDef"), $("#UArenaType"), $("#UAtk"),  ]
                            HoverAreas = [
                                $("#UCost"), $("#UTitle"), $("#UArenaType"), $("#UHps"), $("#UAtk"), $("#UDef"), $("#URec"), $("#UImpHps"), $("#UImpAtk"), $("#UImpDef"), $("#UImpRec"),
                                $("#ULS"), $("#UES"), $("#UBB"), $("#USBB"), $("#UUBB"), $("#UTypeAClk"), $("#UTypeBClk"), $("#UTypeGClk"), $("#UTypeLClk"), $("#UTypeOClk")
                            ];
                            $.each(HoverAreas, function(index, value) {
                                value.mouseenter(function () {
                                    // output raw json data (for now) about the skill to the extra info window
                                    var u = Units[SelectedIndex];
                                    var s = "";
                                    switch (value["selector"]) {
                                        case "#UArenaType":
                                            switch (u["ai"]) {
                                            case "1":
                                                s = "<p>60% chance to use BB/SBB with no requirements. </p><p><strong>Position doesn't matter.</strong></p>";
                                                break;
                                                case "2":
                                                    s = "<p>68% chance to use BB/SBB if any enemy is above 50% HPs, 20% chance otherwise.</p><p><strong>Position near the top of the team.</strong></p>";
                                                    break;
                                                case "3":
                                                    s = "<p>68% chance to use BB/SBB with no requirements.</p><p><strong>Position doesn't matter.</strong></p>";
                                                    break;
                                                case "4":
                                                    s = "<p>72% chance to use BB/SBB if any enemy is below 50% HPs, 30% chance otherwise.</p><p><strong>Position near the bottom of the team.</strong></p>";
                                                    break;
                                                case "5":
                                                    s = "<p>84% chance to use BB/SBB if any party member is below 50% HPs, 20% chance otherwise.</p><p><strong>Position doesn't matter.</strong></p>";
                                                    break;
                                                case "6":
                                                    s = "<p>100% chance to use BB/SBB if any party(healer unit) or enemy(dps unit) is below 25% HPs, 0% chance otherwise.</p><p><strong>Position toward the middle/bottom of the team.</strong></p>";
                                                    break;
                                                case "7":
                                                    s = "<p>100% chance to use BB/SBB if any party member is below 75% HPs, 0% chance otherwise.</p><p><strong>Position doesn't matter.</strong></p>";
                                                    break;
                                            }
                                            break;
                                    case "#ULS":
                                        s = u["leader skill"] === "<NONE>" ? "<strong>NONE</strong>" : JSON.stringify(JSON.parse(u["leader skill"])["effects"], null, 2);
                                        break;
                                    case "#UES":
                                        s = u["extra skill"] === "<NONE>" ? "<strong>NONE</strong>" : JSON.stringify(JSON.parse(u["extra skill"])["effects"], null, 2);
                                        break;
                                    case "#UBB":
                                        s = u["bb"] === "<NONE>" ? "<strong>NONE</strong>" : JSON.stringify(JSON.parse(u["bb"])["levels"][9]["effects"], null, 2);
                                        break;
                                    case "#USBB":
                                        s = u["sbb"] === "<NONE>" ? "<strong>NONE</strong>" : JSON.stringify(JSON.parse(u["sbb"])["levels"][9]["effects"], null, 2);
                                        break;
                                    case "#UUBB":
                                        s = u["ubb"] === "<NONE>" ? "<strong>NONE</strong>" : JSON.stringify(JSON.parse(u["ubb"])["levels"][9]["effects"], null, 2);
                                        break;
                                    }
                                    // output the data and change the font size if needed
                                    $("#UExInfo").css("font-size", (s.length > 800 ? "x-small" : s.length > 565 ? "smaller" : s.length > 200 ? "inherit" : "large")).html(s);
                                }).mouseleave(function (a) {
                                    $("#UExInfo").html("");
                                });
                            });
                        });

                        /**
                         * called when a unit or sphere div is clicked
                         * toggle the visibility of the search form and info window
                         */
                        function OnButtonClick(index) {
                            // global copy for lookahead source
                            SelectedIndex = index;

                            // configure typeahead
                            configure();

                            // change the placeholder text in the search form and erase any current input
                            if (SelectedIndex < 6)
                                $("#q").attr("placeholder", "UNIT" + (SelectedIndex + 1) + "  : Unit Name, Rarity, Element").val("");
                            else
                                $("#q").attr("placeholder", "UNIT" + (SelectedIndex % 6 + 1) + "(SPHERE " + (SelectedIndex < 12 ? 1 : 2) + ") : Sphere Name").val("");

                            // show the form
                            $("#form").show("fast");

                            // give focus to text box
                            $("#q").focus();

                            // show the unit info window
                            UnitInfoBox();
                        }

                        /**
                         * Toggles all unit type buttons off and enables the correct one
                         */
                        function InitTypeButtons() {
                            $.each(TypeBtns, function (index, value) {
                                var e = value["selector"].replace("Clk", "");
                                $(e).hide(0);
                            });
                            var t = UnitTypes[SelectedIndex];
                            var e ="#UType" + (t === "anima" ? "A" : t === "breaker" ? "B" : t === "guardian" ? "G" : t === "_lord" ? "L" : "O" );
                            $(e).show();
                        }

                        /**
                         * Shows info for the hovered unit or sphere.
                         */
                        function UnitInfoBox() {
                            // show info about the hovered unit, do nothing if the slot is empty
                            if (Units[SelectedIndex] === undefined) return;

                            InitTypeButtons();
                            var u = Units[SelectedIndex];
                            $("#UInfoWindow").children("h2").html(u["name"]);
                            $("#UTitle").html(u["rarity"] + "* " + u["name"]).css("color", u["element"] === "FIRE" ? "red" : u["element"] === "WATER" ? "#008fd8" : u["element"] === "THUNDER" ? "yellow" :
                                u["element"] === "EARTH" ? "green" : u["element"] === "LIGHT" ? "white" : u["element"] === "DARK" ? "purple" : "white");
                            $("#UCost").html(u["cost"]);
                            $("#UArenaType").html(u["ai"]);
                            $("#ULS").html(u["leader skill"] === "<NONE>" ? "<strong>NONE</strong>" : "<strong>" + JSON.parse(u["leader skill"])["name"] + "</strong> - " + JSON.parse(u["leader skill"])["desc"]);
                            $("#UES").html(u["extra skill"] === "<NONE>" ? "<strong>NONE</strong>" : "<strong>" + JSON.parse(u["extra skill"])["name"] + "</strong> - " + JSON.parse(u["extra skill"])["desc"]);
                            $("#UBB").html(u["bb"] === "<NONE>" ? "<strong>NONE</strong>" : "<strong>" + JSON.parse(u["bb"])["name"] + "</strong> - " + JSON.parse(u["bb"])["desc"]);
                            $("#USBB").html(u["sbb"] === "<NONE>" ? "<strong>NONE</strong>" : "<strong>" + JSON.parse(u["sbb"])["name"] + "</strong> - " + JSON.parse(u["sbb"])["desc"]);
                            $("#UUBB").html(u["ubb"] === "<NONE>" ? "<strong>NONE</strong>" : "<strong>" + JSON.parse(u["ubb"])["name"] + "</strong> - " + JSON.parse(u["ubb"])["desc"]);
                            $("#UHps").html((UnitTypes[SelectedIndex] === "anima" ? Math.floor((JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["hp max"] + JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["hp min"]) / 2) :
                                JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["hp"]) + parseInt(JSON.parse(u["imp"])["max hp"])); //add sphere stats here
                            $("#UAtk").html((UnitTypes[SelectedIndex] === "breaker" ? (JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["atk max"] + JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["atk min"]) / 2 :
                                JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["atk"]) + parseInt(JSON.parse(u["imp"])["max atk"])); //add sphere stats here
                            $("#UDef").html((UnitTypes[SelectedIndex] === "breaker" || UnitTypes[SelectedIndex] === "guardian" || UnitTypes[SelectedIndex] === "oracle" ?
                                (JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["def max"] + JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["def min"]) / 2 :
                                JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["def"]) + parseInt(JSON.parse(u["imp"])["max def"])); //add sphere stats here
                            $("#URec").html((UnitTypes[SelectedIndex] === "anima" || UnitTypes[SelectedIndex] === "guardian" || UnitTypes[SelectedIndex] === "oracle" ?
                                (JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["rec max"] + JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["rec min"]) / 2 :
                                JSON.parse(u["stats"])[UnitTypes[SelectedIndex]]["rec"]) + parseInt(JSON.parse(u["imp"])["max rec"])); //add sphere stats here
                            $("#UImpHps").html(parseInt(JSON.parse(u["imp"])["max hp"]));
                            $("#UImpAtk").html(parseInt(JSON.parse(u["imp"])["max atk"]));
                            $("#UImpDef").html(parseInt(JSON.parse(u["imp"])["max def"]));
                            $("#UImpRec").html(parseInt(JSON.parse(u["imp"])["max rec"]));

                            // show the window
                            $("#UInfoWindow").show("fast");
                        }


                        /**
                         * Hides the search box and resets the SelectedElement var
                         */
                        function HideForm() {
                            $("#form").hide("fast");
                        }

                        /**
                         * Configures typeahead
                         */
                        function configure()
                        {
                            // configure typeahead
                            // https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md
                            $("#q").typeahead({
                                autoselect: true,
                                highlight: true,
                                minLength: 1
                            },
                            {
                                source: search,
                                templates: {
                                    empty: "nothing found yet",
                                    suggestion: _.template("<p><%- name %><% if (typeof(rarity) !== 'undefined')" +
                                        "{ %>, <%- rarity %>*<% }" +
                                        " else if (typeof(sphere_type_text) !== 'undefined')" +
                                        "{ %>, <%- sphere_type_text %><% " +
                                        "} %></p>")
                                }
                            });

                            // get image for selected unit from http://bravefrontierglobal.wikia.com/
                            // using the wikia API http://bravefrontierglobal.wikia.com/api/v1/#!/Articles/getDetails_get_1
                            // ex.: http://bravefrontierglobal.wikia.com/api.php?action=query&titles=Gazia&prop=imageinfo&iiprop=url&format=json
                            $("#q").on("typeahead:selected", function (eventObject, suggestion, name) {

                                if (SelectedIndex < 6) {

                                    // create the query string for the api
                                    var parameters = { query: encodeURI(suggestion.name) + "&abstract=100&width=115&height=299" }

                                    // get the image for this unit
                                    $.getJSON("api.php", parameters)
                                        .done(function (data, textStatus, jqXHR) {
                                            //get the image title and url from the fetched obj
                                            var title = "", thmb = "";
                                            var obj = data["items"][Object.keys(data["items"])[0]];
                                            if (obj) {
                                                title = obj["title"];
                                                thmb = obj["thumbnail"];
                                            }

                                            // insert the data into the correct div
                                            var elem = "#u" + (SelectedIndex + 1).toString();
                                            $(elem).children("img").attr({ src: thmb, alt: title });
                                            elem = "#ut" + (SelectedIndex + 1).toString();
                                            var t = title.split(" ");
                                            var o = "<h5>" + suggestion["rarity"] + "* ";
                                            for (var i = 0; i < t.length - 1; i++) {
                                                o += t[i] + " ";
                                            }
                                            o += "</h5><h4>" + t[t.length - 1] + "</h4>";
                                            $(elem).html(o);
                                            // store the selected unit data in the Units array
                                            Units[SelectedIndex] = suggestion;

                                            // reset unit type
                                            UnitTypes[SelectedIndex] = "_lord";

                                            // hide query box
                                            HideForm();

                                            // show unit info window
                                            UnitInfoBox();
                                        })
                                        .fail(function(jqXHR, textStatus, errorThrown) {
                                            // log error to browser's console
                                            console.log(errorThrown.toString());
                                        });
                                } else {

                                    // insert the data into the correct div
                                    var uo = (SelectedIndex % 6 + 1).toString() + "_" + (SelectedIndex < 12 ? 1 : 2).toString();
                                    $("#sphTxt" + uo).css("font-size", suggestion["name"].length <= 10 ? "" : suggestion["name"].length < 24 ? "small" : "smaller");
                                    $("#sphTxt" + uo).css("padding-top", suggestion["name"].length <= 12 ? "5px" : suggestion["name"].length < 24 ? "2px" : "0");
                                    $("#sphTxt" + uo).html(suggestion["name"]);
                                    $("#sphOrb" + uo).show("slow");

                                    // store the selected sphere data in the Spheres array
                                    HideForm();
                                    Spheres[SelectedIndex - 6] = suggestion;
                                }
                            });

                            // hide text box when it loses focus
                            $("#q").focusout(function(eventData) {
                                HideForm();
                            });

                        }

                        /**
                         * Searches database for typeahead's suggestions.
                         */
                        function search(query, cb)
                        {
                            // get places matching query (asynchronously
                            var parameters = {
                                input: query
                            };
                            $.getJSON(SelectedIndex < 6 ? "searchUnit.php" : "searchSphere.php", parameters)
                            .done(function(data, textStatus, jqXHR) {
                                // call typeahead's callback with search results (i.e., places)
                                cb(data);
                            })
                            .fail(function(jqXHR, textStatus, errorThrown) {
                                // log error to browser's console
                                console.log(errorThrown.toString());
                            });
                        }




                    
Copyright © Nicholus Huber / Crux Gaming