Code in the SBA Project

The part of the Snake Battle Arena sample for SmartGlass that runs on the Xbox One console is a game, but this game is implemented as an HTML 5 app using the Application Development Kit (ADK). As a result, the SBA project for this game includes HTML, cascading stylesheet (CSS), and JavaScript files. The game includes SmartGlass features by using the SmartGlass Console API.

Although the Snake Battle Arena sample is implemented with the ADK and uses JavaScript, the sample can also serve as an example to game developers who use the XDK of how a SmartGlass experience is constructed end-to-end and how the Xbox One console and SmartGlass-enabled devices interact in that experience.

The SBA project includes the following files:

Default.html

The Default.html file is an HTML file that the Xbox One console uses to display the Snake Battle Arena game.

The <head> section of this HTML file includes two groups of lines that reference additional files that are needed to support the successful operation of that game. The first of these groups includes WinJS files.

<!-- WinJS references -->
<link href="//Microsoft.Xbox.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<link href="//Microsoft.Xbox.WinJS.1.0/css/xbox-ui-dark.css" 
    rel="stylesheet" />
<script src="//Microsoft.Xbox.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.Xbox.WinJS.1.0/js/ui.js"></script>
<script src="//Microsoft.Xbox.WinJS.1.0/js/xbox.js"></script>  

The second group references the cascading stylesheet and JavaScript files that make up the rest of the game project.

<!-- Sample references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
<script src="/js/snake.js"></script>  

The <body> section of the Default.html defines the layout of the game as it appears on the monitor for the Xbox One console. The body consists of an outer <div> section that has a class name of win-layout-pagegrid and includes two <div> sections nested directly inside. The first of these nested <div> sections contains another <div> element nested inside of it that specifies a heading that the game displays at the top of the screen.

<div class="win-layout-pagegrid">
    <div class="win-layout-titlesafeheader">
        <div class="win-text-pagetitle win-layout-pagetitle">Snake Battle Arena</div>
    </div>
    <!-- The`<body>` section continues in the next example. -->  

The second of the <div> sections that is nested in the outermost win-layout-pagegrid <div> section consists of a table that specifies the layout the parts of the game display that change throughout the game. The first row the includes two cells. The left cell consists of a <canvas> element specifies a canvas that functions as the playing area through which the snakes in the game move. The right cell displays the high score for the current game and the scores for up to 16 players, because a maximum of 16 devices can connect to the Xbox One console and play the game.

    <!-- Continues the`<body>` and outermost win-layout-pagegrid`<div>` 
        sections from the previous example. -->
        <div class="win-layout-titlesafecontent">
            <table>
                <tr>
                    <td>
                        <canvas id="canvas" width="800" height="600"></canvas>
                    </td>
                    <td valign="top">
                        <div id="hiscore"></div>
                        <div id="p1"></div>
                        <div id="p2"></div>
                        <div id="p3"></div>
                        <div id="p4"></div>
                        <div id="p5"></div>
                        <div id="p6"></div>
                        <div id="p7"></div>
                        <div id="p8"></div>
                        <div id="p9"></div>
                        <div id="p10"></div>
                        <div id="p11"></div>
                        <div id="p12"></div>
                        <div id="p13"></div>
                        <div id="p14"></div>
                        <div id="p15"></div>
                        <div id="p16"></div>
                    </td>
                </tr>
                <!-- The`<body>` section continues in the next example. -->  

The second row of the table consists of a single cell that the game uses to diplay the messages and that the game sends to and receives from the SmartGlass-enabled device.

                <!-- Continues the`<body>` and outermost 
        win-layout-pagegrid`<div>` and`<table>` sections from the 
        previous example. -->
                    <tr>
                    <td colspan="2">
                        <p id="key">Connect your SmartGlass device to play!</p>
                    </td>
                </tr>
            </table>
        </div>
    </div>
    <!-- The`<body>` section ends here. -->  

Default.js

The Default.js file in the SBA project mainly consists of code for initilizaiton of the HTML 5 app. For the SmartGlass experience, the key part of the code in this file is the section that specifies an anonymous function to handle the WinJS.Application.onready event. This anonymous function calls the SGHandler.Initialize function in the Snake.js script to initialize SmartGlass when the HTML 5 app is ready.

app.onready = function (args) 
{
    SGHandler.Initialize();

}  

For information about the SGHandler.Initialize function, see Initializing SmartGlass.

Snake.js

The Snake.js script contains the majority of the code for the Snake Battle Arena game that runs on the Xbox One console. The script contains an initial section that sets up a function to serve as a template for the snakes in the game, and sets up variables that are used in the rest of the script. The remainder of the script consists mainly of functions that perform various tasks. The SmartGlass-related tasks that the script performs include:

The tasks that script performs that are largely independent of SmartGlass include:

Creating the Snake Template Function and Setting Up Variables

The first section of this script sets up a function to serve as a template for the snakes in the game, and sets up variables that are used in the rest of the script. The template function for the snakes sets initial values for various propreties of the snake, such as its name, color, and direction of travel. For the SmartGlass experience, the key property is the deviceId property, which specifies the device identifier for the SmartGlass-enabled device that controls the movement of the snake.

// Snake "Class"
function Snake(color) 
{
    // The direction of the snake.
    this.d = "right";

    // The score associated with this specific snake.
    this.score = 0;

    // The color of the snake.
    this.color = color;

    // Whether to draw the snake.
    this.isActive = false;

    // An array representation of the body of the snake.
    this.body = [];

    // The name of the snake.
    this.name = "Snake";

    // The device identifier associated with this snake.
    this.deviceId = 0;

    // Start the snake at some random location.
    this.xi = Math.round(Math.random() * ((w / (2 * cw))));
    this.yi = Math.round(Math.random() * ((h / (2 * cw))));

    for (var i = 5; i > 0; i--) 
    {
        this.body.push({
            x: i + this.xi,
            y: this.yi
        });
    }
}  

The next section of the code creates variables to track various items during the game.

// Variables for the canvas control.
var canvas;
var context;
var cw = 10; // cell width
var w;
var h;

// Variable representing the food that the snakes try to find.
var food;

// Variables to keep track of the high score for the game.
var hiscore = 0;
var hiscorePlayerNum = 1;

// Variables for initializing rendering.
var fps = 10;
var requestId = 0;

// Variables to represent the snakes.
var snakes = [];
var defaultColor = "blue";  

The next line starts the game when the window for the game loads by setting the event handler for the window.onload event to the init function in the script that initializes the game.

// Start the game when the window loads.
window.onload = init;  

Finally, this initial section of the code create variables for some SmartGlass-specific items. For information about how these items are used in the script, see Initializing SmartGlass.

// SmartGlass variables.
var watcher;
var deviceList = [];  

Initializing SmartGlass

The window.SGHandler class defines an Initialize function that performs the following tasks to initialize SmartGlass for the Snake Battle Arena game:

The SGHandler.SendToClients function provides a way to send a message to all of the devices that are connected to the Xbox One console at once, but this function is not currently used by the sample.

window.SGHandler = 
{
    Initialize: function () 
    {
        // Create a SmartGlassDeviceWatcher object.
        watcher = new Windows.Xbox.SmartGlass.SmartGlassDeviceWatcher();

        // When a device is added or removed, call the sgDeviceAdded or 
        // sgDeviceRemoved functions.
        watcher.ondeviceadded = sgDeviceAdded;
        watcher.ondeviceremoved = sgDeviceRemoved;

        // Because the app might start after devices connect to the console, query for the existing
        // devices the console knows about by calling the SmartGlassDevice.findAllAsync function. When the 
        // findAllAsync promise completes, loop through the collection of devices that were found and call the same 
        // event handler that is called when new devices are added.
        Windows.Xbox.SmartGlass.SmartGlassDevice.findAllAsync().then(function (devices) 
        {
            for (var i = 0; i < devices.length; i++) 
            {
                sgDeviceAdded(devices[i]);
            }
        }, function (error) 
        {
            trace("Error getting all the existing connected devices.");
        });
    },
    SendToClients: function (message) 
    {
        // Loop through all known devices and send them the message.
        for (var i = 0; i < deviceList.length; i++) 
        {
            sgSend(deviceList[i], message);
        }
    }
}  

For more information about initializing SmartGlass in an Xbox One title, see Receiving Notifications about the Addition and Removal of SmartGlass-Enabled Devices.

Responding to the Addition of a SmartGlass-enabled Device

When a new SmartGlass-enabled device connects to the Xbox One console, the sgDeviceAdded function runs to handle the event. The sgDeviceAdded was attached to the SmartGlassDeviceWatcher.ondeviceadded event in the SGHandler.Initialize function in the script. For more informaotn about the SGHandler.Initialize function, see Initializing SmartGlass.

The sgDeviceAdded function performs the following tasks:

// Runs when a SmartGlass device is added.
function sgDeviceAdded(device) 
{
    // Log the device.
    trace("Device added: " + device.displayName + "[" + device.id + "]");

    // Add the device to the list of known devices.
    deviceList.push(device);

    // Find the first snake that does not yet have a device identifier.
    for (var i = 0; i < snakes.length; i++) {
        if (snakes[i].deviceId == 0) {
            // Associate the snake with the device.
            snakes[i].deviceId = device.id;

            // Respond to changes in the accelerometer readings.
            device.htmlSurface.sensors.accelerometer.onreadingchanged = 
                function (args) 
            { 

            snakeAccelControl(args, i); 
   
            };

            // Respond to messages from the player.
            device.htmlSurface.onmessagereceived = function (args) 
            { 
                messageFromPlayer(args, i); 
            };

            break;
        }
    }

    // Send a welcome message to the device.
    var message = 
    { 
        message: "You are now joining Snake Battle Royale. All the best!" 
    };
    sgSend(device, message);
}  

For more information about responding to events for the addition of SmartGlass-enabled devices, see Receiving Notifications about the Addition and Removal of SmartGlass-Enabled Devices.

Responding to the Removal of a SmartGlass-enabled Device

When a SmartGlass-enabled device disconnects to the Xbox One console, the sgDeviceRemoved function runs to handle the event. The sgDeviceRemoved function was attached to the SmartGlassDeviceWatcher.ondeviceremoved event in the SGHandler.Initialize function in the script. For more informaotn about the SGHandler.Initialize function, see Initializing SmartGlass.

The sgDeviceRemoved function performs the following tasks:

// Runs when a SmartGlass device is removed.
function sgDeviceRemoved(device) 
{
    trace("Device left: " + JSON.stringify(device));

    // Remove the associations between snakes and devices.
    for (var i = 0; i < snakes.length; i++) 
    {
        if (snakes[i].deviceId == device.id) 
        {
            snakes[i].deviceId = 0;
            break;
        }
    }

    // Remove the device from the local list of devices.
    for (var i = 0; i < deviceList.length; i++) 
    {
        if (deviceList[i].id === device.id) 
        {
            deviceList.splice(i, 1);
            break;
        }
    }
}  

For more information about responding to events for the removal of SmartGlass-enabled devices, see Receiving Notifications about the Addition and Removal of SmartGlass-Enabled Devices.

Sending a Message to a SmartGlass-enabled Device

The Snake Battle Arena game sends messages to the game players on their SmartGlass-enabled devices, such as a message that welcomes a player to the game when they join the game. The sgSend function in the Snake.js script is the function that sends these messages. For an example of how the sgSend function gets called in the Snake.js script, see Responding to the Addition of a SmartGlass-enabled Device. The sgSend function takes the JSON message that the game is going to send as a parameter.

The sgSend function performs the following tasks:

function sgSend(device, msgObject) 
{
    trace("Sending message: " + JSON.stringify(msgObject) + " to device " + device.id);

    // To send a JSON message to the client, use the htmlSurface property for the device and 
    // make sure that the message is actually JSON. A good practice is to always pass an object to the
    // your send function and then stringify it just before you send it.
    // Note: The client does not require the active surface to be an htmlSurface to
    //       send and receive messages.
    device.htmlSurface.submitMessageAsync(JSON.stringify(msgObject));
}  

For more information about how to send messages from an Xbox One console to SmartGlass-enabled devices, see Sending Messages from an Xbox One Console to a SmartGlass-Enabled Device.

For information about how the SmartGlass companion in the Snake Battle Arena sample receives the messages that the game sends, see Specifying Handlers for SmartGlass Events.

Receiving a Message from a SmartGlass-enabled Device

In the SmartGlass experience for Snake Battle Arena, the game receives a message from the SmartGlass-enabled device that a player is using when the player indicates that they want to join the game. The messageFromPlayer function is the event handler in the game code that processes these messages when they are received. For an example of how the messageFromPlayer function gets called in the Snake.js script, see Responding to the Addition of a SmartGlass-enabled Device. The messageFromPlayer function has parameters for an object that contains information about the message and the index of the snake for the player from which the message was received.

The messageFromPlayer function performs the following tasks:

// Handler for messages from the player.
function messageFromPlayer(args, snakeIndex) 
{
    // When the game receives a message,  the game receives an object with 
    // these parameters:
    //   message: The JSON message that the device sent.
    //    target: The SmartGlassHtmlSurface object that the corresponds to the 
    //            device that sent the message.

    // Show what the player said.
    var playerNumber = snakeIndex + 1;
    trace("Player " + playerNumber + " said: " + args.message);

    // Convert the message to an object.
    var obj = JSON.parse(args.message);

    // Process the name of the snake.
    if (obj.name != undefined) 
    {
        snakes[snakeIndex].name = obj.name;
    }

    // Process the color of the snake.
    if (obj.color != undefined) 
    {
        snakes[snakeIndex].color = obj.color;
    }

    // Process the direction of the snake.
    if (obj.d != undefined) 
    {
        directionHandler(obj.d, snakeIndex);
    }
}  

For more information about how to receive messages from SmartGlass-enabled devices in a title on the Xbox One console, see Receiving Messages from a SmartGlass-Enabled Device in an Xbox One Game or App.

For information about how the SmartGlass companion in the Snake Battle Arena sample sends messages to the game, see Adding a Player Using the Accelerometer to the Game and Handling Events from the Controller Buttons.

Receiving Accelerometer Data from a SmartGlass-enabled Device

The Snake.js script includes the SnakeAccelControl function that processes the accelerometer data the game receives from each SmartGlass-enabled device. For an example of how the SnakeAccelControl function gets called in the Snake.js script, see Initializing SmartGlass. The SnakeAccelControl function has parameters for an object that represents an accelerometer reading and the index of the snake for the player from which the accelerometer reading was received.

The SnakeAccelControl function performs the following tasks:

// Handler for the accelerometer readings.
function snakeAccelControl(args, snakeIndex) {
    var X = args.reading.accelerationX;
    var Y = args.reading.accelerationY;
    var Z = args.reading.accelerationZ;

    if (Y < -0.25) {
        directionHandler("down", snakeIndex);
    }
    if (Y > 0.25) {
        directionHandler("up", snakeIndex);
    }
    if (X < -0.25) {
        directionHandler("left", snakeIndex);
    }
    if (X > 0.25) {
        directionHandler("right", snakeIndex);
    }
}  

For more information about receiving accelerometer readings in an Xbox One game or app, see Receiving Accelerometer Data from a SmartGlass-Enabled Device

For information about how the companion in the Snake Battle Arena sample sends accelerometer readings to the game, see Adding a Player Using the Accelerometer to the Game.

Initializing the Game

The code to initialize the Snake Battle Arena game occurs in the init function in the Snake.js script. This function runs when the window loads, because the function was attached to the window.onload event in the initial section of the Snake.js script in which variables were declared. For an example that shows how the init was attached to the window.onload event, see Creating the Snake Template Function and Setting Up Variables.

The init function performs the following tasks:

// Initialize the game.
function init() 
{
    // Initialize the canvas.
    canvas = document.getElementById('canvas');
    context = canvas.getContext('2d');
    w = canvas.clientWidth;
    h = canvas.clientHeight;

    // Initialize food for the first time.
    create_food();

    // Create 16 inactive snakes.
    for (var i = 0; i < 16; i++) {
        snakes.push(new Snake(defaultColor));
    }

    // Start animating frames.
    requestId = window.requestAnimationFrame(paint);
}  

Displaying Messages on the Monitor for the Xbox One Console

Many of the functions in the Snake.js script call the trace function to show messages at the bottom of the display for the game. The trace function takes the message as a parameter, and adds the message to the beginning of the content of the cell in the second row of the table that the Default.html file defines.

// Log messages on screen
function trace(s) 
{
    document.getElementById("key").innerHTML = s + "

" + 
        document.getElementById("key").innerHTML;;
}  

Setting the Direction of the Snake

In the Snake Battle Arena game, the direction of a snake is changed according to data received from the accelerometer on a SmartGlass-enabled device, or from console buttons tapped on a SmartGlass-enabled device. The script interprets this information to determine how to change direction of a snake, and then uses the directionHandler function to make the change. For examples that call the directionHandler function, see Receiving a Message from a SmartGlass-enabled Device and Receiving Accelerometer Data from a SmartGlass-enabled Device.

The directionHandler function takes a direction and the index of the snake that should change direction as parameters, and then sets the d property for the snake with that index to the appropriate direction.

// Handle changes in the direction of a snake.
function directionHandler(d, snakeIndex) 
{
    if (!snakes[snakeIndex].isActive) 
    {
        snakes[snakeIndex].isActive = true;
    }

    if (d == "down" && snakes[snakeIndex].d != "up") 
    {
        snakes[snakeIndex].d = "down";
    }
    if (d == "up" && snakes[snakeIndex].d != "down") 
    {
        snakes[snakeIndex].d = "up";
    }
    if (d == "left" && snakes[snakeIndex].d != "right") 
    {
        snakes[snakeIndex].d = "left";
    }
    if (d == "right" && snakes[snakeIndex].d != "left") 
    {
        snakes[snakeIndex].d = "right";
    }
}  

Positioning the Food for the Snakes

The snakes in the Snake Battle Arena game move around to try to capture food and grow longer. The game used the create_food function to put new food in a random location when the game starts and whenever a snake captures the food.

For examples that call the directionHandler function, see Initializing the Game and Drawing the Playing Area.

// Create the food.
function create_food() 
{
    food = {
        x: Math.round(Math.random() * ((w / (2 * cw)))),
        y: Math.round(Math.random() * ((h / (2 * cw))))
    };
}  

Determining if a Snake Crashed into the Walls or Another Snake

In the Snake Battle Arena game, a snake dies and the player has to start over if the snake crashes into the walls the surround the playing area or into another snake. The set of functions in the following example determines when these circumstances occur. For an example that calls the isCollision function, see Moving the Snakes.

function isLeftCrash(nx) 
{
    return (nx < 0);
}

function isRightCrash(nx) 
{
    return (nx == w / cw);
}

function isTopCrash(ny) 
{
    return (ny < 0);
}

function isBottomCrash(ny) 
{
    return (ny == h / cw);
}

// Determine if a collision happened. Ignore the collision if the 
// snake collided with its own body.
function isCollision(nx, ny, snakeNum) 
{
    if (isLeftCrash(nx)) 
    {
        return true;
    }
    if (isRightCrash(nx))
    {
        return true;
    }
    if (isTopCrash(ny)) 
    {
        return true;
    }
    if (isBottomCrash(ny)) 
    {
        return true;
    }

    for (var i = 0; i < snakes.length; i++) 
    {
        if (snakes[i].isActive && i != snakeNum) 
        {
            if (check_collision(nx, ny, snakes[i].body)) 
            {
                return true;
            }
        }
    }
}

function check_collision(x, y, array) 
{
    // This function checks if the provided x and y coordinates exist
    // in an array of cells.
    for (var i = 0; i < array.length; i++) 
    {
        if (array[i].x == x && array[i].y == y)
            return true;
    }
    return false;
}  

Moving the Snakes

The move_snakes function contains the code that moves that snakes through the playing area in the Snake Battle Arena game. For an example the calls the move_snakes function, see Drawing the Playing Area.

The move_snakes function performs the following tasks for each snake:

// Move any and all snakes.
// The logic is to pop out the tail cell and place it in front of 
// the head cell.
function move_snakes() 
{
    var nx;
    var ny;

    // Move the snakes.
    for (var i = 0; i < snakes.length; i++) 
    {
        if (snakes[i].isActive) 
        {
            nx = snakes[i].body[0].x;
            ny = snakes[i].body[0].y;

            // Move the snake based on its direction.
            if (snakes[i].d == "right") nx++;
            else if (snakes[i].d == "left") nx--;
            else if (snakes[i].d == "up") ny--;
            else if (snakes[i].d == "down") ny++;

            // Reset the snake if a collision occurs.
            if (isCollision(nx, ny, i)) 
            {
                var deviceId = snakes[i].deviceId;
                var snakeName = snakes[i].name;
                var snakeColor = snakes[i].color;
                snakes[i] = new Snake(snakeColor);
                snakes[i].name = snakeName;
                snakes[i].deviceId = deviceId;
            }

            // Paint the snake.
            if (snakes[i].isActive) 
            {
                // Make the snake eat the food.
                // The logic is that if the new head position matches the 
                // position of the food, create a new head instead of moving 
                // the tail.
                if (nx == food.x && ny == food.y) 
                {
                    var tail = { x: nx, y: ny };
                    snakes[i].score++;

                    // Create new food.
                    create_food();
                }
                else 
                {
                    // Pop out the last cell.
                    var tail = snakes[i].body.pop(); 
                    tail.x = nx;
                    tail.y = ny;
                }

                // Put the tail back as the first cell.
                snakes[i].body.unshift(tail); 

                for (var j = 0; j < snakes[i].body.length; j++) 
                {
                    var c = snakes[i].body[j];
                    paint_cell(c.x, c.y, snakes[i].color);
                }
            }
        }

        // Update high score as necessary.
        if (snakes[i].score > hiscore) 
        {
            hiscore = snakes[i].score;
            hiscorePlayerNum = i + 1;
            fps = 10 + (hiscore / 2);
        }
    }
}  

Drawing the Playing Area

The functions that this document described so far determined where snakes and food should appear on the playing field, but have not drawn these items on the screen. The paint and paint_cell functions perform the tasks related to drawing the snakes, by using the methods and properties of the CanvasRenderingContext2D object that stored in the context variable in the init function that initialized the game. For information about the init function, see Initializing the Game.

The paint_cell function fills a cell on the playing field with the specified color. The paint_cell function is called from the paint and the move_snakes functions. For information about the move_snakes function, see Moving the Snakes.

The paint function does most of the drawing work. The paint function is specified as an event handler to call when the Xbox One console by calls to the Window.requestAnimationFrame function that occur in the init function and the paint function itself.

The paint function performs the following tasks:

// Render everything, including the canvas, snake, and food.
function paint() 
{
    // Keep the framerate somewhat consistent.
    window.cancelAnimationFrame(requestId);

    // To avoid the snake trail, paint the background on every frame.
    context.fillStyle = "white";
    context.fillRect(0, 0, w, h);
    context.strokeStyle = "black";
    context.strokeRect(0, 0, w, h);

    // Move and paint the snakes.
    move_snakes();

    // Display the food. 
    paint_cell(food.x, food.y);

    // Display the score as appropriate.
    document.getElementById("hiscore").innerText = "High Score: " + hiscore + 
        " (Player " + hiscorePlayerNum + ", " + snakes[hiscorePlayerNum - 1].name + ")";
    for (var i = 0; i < 16; i++) 
    {
        if (snakes[i].isActive) 
        {
            var playerNum = i + 1;
            var playerId = "p" + playerNum;
            var playerText = "Player " + playerNum + "(" + snakes[i].name + "): ";
            document.getElementById(playerId).innerText = playerText + snakes[i].score;
        }
    }

    // Animate the snake.
    setTimeout(function () 
    {
        requestId = window.requestAnimationFrame(paint);
    }, 1000 / fps);
}

// Create a generic function to paint cells.
function paint_cell(x, y, colorFill) 
{
    context.fillStyle = colorFill;
    context.fillRect(x * cw, y * cw, cw, cw);
    context.strokeStyle = "white";
    context.strokeRect(x * cw, y * cw, cw, cw);
}  

See also

Exploring an End-to-End Sample for a SmartGlass Experience: Snake Battle Arena

Code in the CompanionSample Project

Overview of the SmartGlass Platform