Jump to main content | Jump to Primary Navigation | Jump to Sub Navigation


 

Tutorial 5: Pong

Previous tutorial Next tutorial

This tutorial explores accessing remote properties from other hosts, without having to write two separate programs or configuration files.

The example provided is Pong, a classic computer game. Both players run the same program and use the same configuration file. A maximum of two players can participate. Player 1's program controls the ball, so no 'server' is necessary.

As in the previous tutorials, it is necessary to initialize hosts and bring up local devices. However, this tutorial introduces a new kind of host intialization supporting networking. It also combines using a configuration file (Tutorial 2: The XML configuration file) and programmatic device creation (Tutorial 3: Creating devices programmatically) on the same host. The full code for this tutorial is in pong.c

( Be aware that the following example depends on SDL [Simple DirectMedia Layer], an open-source graphics library. However, the code below abstracts out any SDL calls by prefixing the function with sdl_ ).

pong.png

Contents

  1. Multiple Hosts: Who are we?
  2. 'Pong' Drivers
  3. Player 1: Pong Ball
  4. Player 2: Much Easier
  5. Starting both Hosts
  6. Looking up Properties
  7. Setting the Bat position
  8. Initialize video, Minimise blitting
  9. Handle keyboard input, Go!
  10. Handle Up/Down
  11. Wrapping Up
  12. Third-Party Libraries

Multiple Hosts: Who are we?

Because we're combining both players' clients into one program and configuration file, we need to determine which host we are. The following code achieves this (but is by no means the only way to do it).

#define PLAYER_1 "127.0.0.1:1234"
#define PLAYER_2 "127.0.0.1:5678"
...
int main(void)
{
...
    host = bot_host_network_create(PLAYER_1);
    if (!host) {
        host = bot_host_network_create(PLAYER_2);
        if (!host) {
            goto exit_out;
        } else {
            player2 = TRUE;
        }
    } else {
        player1 = TRUE;
    }
...
exit_out:
    // stop the host
    if (host)
        bot_host_stop(host);

    // clean up after SDL
    shutdown_pong();
    return 0;
}

The function bot_host_network_create() above is similar to bot_host_create() except that it also enables an HTTP server and binds to the specified IP:port. If binding fails, or the program cannot find a network interface with this IP address, NULL is returned.

The code above ensures that if the program cannot bind to PLAYER_1's address, it will try to bind to PLAYER_2's address. If this still fails, the program will terminate.

(At this point we also set the player1 and player2 booleans which are used later.)

'Pong' Drivers

As in ping-pong, the 'Pong' computer game features two bats and a ball. The design taken here split the game into pong_client.c and pong_ball.c. The pong_client, implemented as a DevBot driver, provides the necessary properties for either Player (X, Y, Score, etc.). The pong_ball, also implemented as a driver, provides the ball physics and must have a pair of pong_client drivers connected to it, so it can detect object collision and adjust the ball trajectory.

Player 1: Pong Ball

Since Player 1 controls handles the Ball physics, and the Ball needs to know how to access Player 1/2's Client device, we can use a combination of configuration file (which does not mention IP address, which may change) and programmatic means of setting properties, prior to starting the Ball device.

    BotProperty *ball_p1, *ball_p2;
...
    if (player1) {
        bot_configure_file(host, "pong.xml", "Player1");
        ball_p1 = bot_host_register_property(host, PLAYER_1 "/Ball/Player1");
        ball_p2 = bot_host_register_property(host, PLAYER_1 "/Ball/Player2");
        bot_property_set_string(ball_p1, PLAYER_1 "/Client");
        bot_property_set_string(ball_p2, PLAYER_2 "/Client");
    }

As can be seen above, bot_host_register_property() can be used to register a remote property, as well as local properties. For example, the URLs above (after the C preprocessor has run over the above code) look like "192.168.1.10:1234/Ball/Player1". This Host/Device/Property scheme can be used to address a property from any device on any network-accessible host.

Player 2: Much Easier

Since Player 2 doesn't have to control the ball, her setup is much easier.

    if (player2)
        bot_configure_file(host, "pong.xml", "Player2");

As can be seen above, the same configuration file can be used, but the "Player2" string defines which <host> section is used from the combined configuration XML.

Starting both Hosts

As with the previous examples, it is now possible to start up both hosts. This starts the Ball going and allows the players to move their bats.

    if (!bot_host_start(host)) {
        bot_warn("Failed to start up host.");
        goto exit_out;
    }

Looking up Properties

Because the SDL renderer requires access to various device properties, it is now necessary to look them up here.

    player1_pos = bot_host_register_property_fail(host,
            PLAYER_1 "/Client/Position");
    player1_dim = bot_host_register_property_fail(host,
            PLAYER_1 "/Client/Dimension");
    player1_score = bot_host_register_property_fail(host,
            PLAYER_1 "/Client/Score");

This also happens for Player 2 and the Ball.

Setting the Bat position

Both players can independently set their initial bat positions. This is done relative to the size of the screen and size of the bat, which are not elaborated on in this tutorial.

    // set bat position for player 1
    if (player1) {
        bot_property_get_uint_all(player1_dim, dim);
        p1_init_pos[0] = 0;
        p1_init_pos[1] = (range[1] / 2) - dim[1];
        bot_property_set_uint_all(player1_pos, p1_init_pos);
    }

    // set bat position for player 2
    if (player2) {
        bot_property_get_uint_all(player2_dim, dim);
        p2_init_pos[0] = range[0] - dim[0];
        p2_init_pos[1] = (range[1] / 2) - dim[1];
        bot_property_set_uint_all(player2_pos, p2_init_pos);
    }

Initialize video, Minimise blitting

The init_pong() function wraps SDL and draws various primitives on the screen. The update_pong() method, passed to the callback handler, updates the screen using SDL when the Ball's X position changes.

    // initialize the video, et al. title is set on player
    init_pong(player1);

    // ensure updates are drawn only when necessary (synced to the ball moving)
    bot_property_add_callback(ball_pos, NULL, update_pong, CALLBACK_RELAXED);

This ensures that a minimum number of updates are drawn to the screen.

Handle keyboard input, Go!

Finally, it is necessary to use SDL to handle keyboard input. The function sdl_keyboard_handler is not covered in depth here, but will block until the user quits the program.

    if (player1) {
        sdl_keyboard_handler(up_pressed, down_pressed, 1);
    } else {
        sdl_keyboard_handler(up_pressed, down_pressed, 2);
    }

The functions up_pressed() and down_pressed() are called when the user presses the Up and Down keys, respectively. They are passed either 1 or 2, depending on the player.

Handle Up/Down

The following code simply increments or decrements the players' Y position dependent on up or down being pressed. 20 pixels was selected for smooth play.

static void up_pressed(int player)
{
    uint32_t pos[2];
    if (player == 1) {
        bot_property_get_uint_all(player1_pos, pos);
        pos[1] -= 20;
        bot_property_set_uint_all(player1_pos, pos);
    } else {
        bot_property_get_uint_all(player2_pos, pos);
        pos[1] -= 20;
        bot_property_set_uint_all(player2_pos, pos);
    }
}


// handle player key-press
static void down_pressed(int player)
{
    uint32_t pos[2];
    if (player == 1) {
        bot_property_get_uint_all(player1_pos, pos);
        pos[1] += 20;
        bot_property_set_uint_all(player1_pos, pos);
    } else {
        bot_property_get_uint_all(player2_pos, pos);
        pos[1] += 20;
        bot_property_set_uint_all(player2_pos, pos);
    }
}

Wrapping Up

The above tutorial yields the following complete file. This example makes use of two special drivers, pong_client.c and pong_ball::c drivers.

/******************************************************************************
 * Implementation of the classic game "Pong", using only devbot and SDL.
 *
 * (c) Edinburgh Robotics 2006-2007
 ******************************************************************************/

#include <devbot/configuration.h>
#include <devbot/dispatcher.h>
#include <devbot/property.h>
#include <devbot/host.h>
#include <devbot/type.h>
#include <devbot/bot.h>

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <glib.h>

#include "pong_sdl.h"

#define PLAYER_1 "127.0.0.1:1234"
#define PLAYER_2 "127.0.0.1:5678"

// properties needed by all
BotProperty *player1_pos, *player2_pos;
BotProperty *player1_score, *player2_score;
BotProperty *ball_pos;
uint32_t range[2];

// handle player key-release
static void up_pressed(int player)
{
    uint32_t pos[2];
    if (player == 1) {
        bot_property_get_uint_all(player1_pos, pos);
        pos[1] -= 20;
        bot_property_set_uint_all(player1_pos, pos);
    } else {
        bot_property_get_uint_all(player2_pos, pos);
        pos[1] -= 20;
        bot_property_set_uint_all(player2_pos, pos);
    }
}


// handle player key-press
static void down_pressed(int player)
{
    uint32_t pos[2];
    if (player == 1) {
        bot_property_get_uint_all(player1_pos, pos);
        pos[1] += 20;
        bot_property_set_uint_all(player1_pos, pos);
    } else {
        bot_property_get_uint_all(player2_pos, pos);
        pos[1] += 20;
        bot_property_set_uint_all(player2_pos, pos);
    }
}


// mainloop of pong game
int main(void)
{
    BotProperty *player1_dim, *player2_dim;
    BotProperty *ball_range;
    BotProperty *ball_p1, *ball_p2;
    BotHost *host;
    uint32_t p1_init_pos[2];
    uint32_t p2_init_pos[2];
    uint32_t dim[2];

    bool player1 = FALSE, player2 = FALSE;

    host = bot_host_network_create(PLAYER_1);
    if (!host) {
        host = bot_host_network_create(PLAYER_2);
        if (!host) {
            goto exit_out;
        } else {
            player2 = TRUE;
        }
    } else {
        player1 = TRUE;
    }

    // Player1 needs Player2's properties
    if (player1) {
        bot_debug("Player2 must connect within 5 seconds...");
        sleep(5);
    }

    /* Player 1's configuration must be augmented with some Client URLs
     * for the Ball device, which requires connecting to the Client
     * devices on both Players.
     */
    if (player1) {
        bot_configure_file(host, "pong.xml", "Player1");
        ball_p1 = bot_host_register_property(host, PLAYER_1 "/Ball/Player1");
        ball_p2 = bot_host_register_property(host, PLAYER_1 "/Ball/Player2");
        bot_property_set_string(ball_p1, PLAYER_1 "/Client");
        bot_property_set_string(ball_p2, PLAYER_2 "/Client");
    }

    // Player 2's configuration is simple, and can be straight from file
    if (player2)
        bot_configure_file(host, "pong.xml", "Player2");

    // start the host's devices and the server up
    if (!bot_host_start(host)) {
        bot_warn("Failed to start up host.");
        goto exit_out;
    }

    // Player2 needs to wait for player 1's Ball properties
    if (player2) {
        bot_debug("Waiting for Player1...");
        sleep(5);
    }

    bot_debug("Type Alt-F4 to exit, or close the window.");

    // player 1's critical properties
    player1_pos = bot_host_register_property_fail(host,
            PLAYER_1 "/Client/Position");
    player1_dim = bot_host_register_property_fail(host,
            PLAYER_1 "/Client/Dimension");
    player1_score = bot_host_register_property_fail(host,
            PLAYER_1 "/Client/Score");

    // player 2's critical properties
    player2_pos = bot_host_register_property_fail(host,
            PLAYER_2 "/Client/Position");
    player2_dim = bot_host_register_property_fail(host,
            PLAYER_2 "/Client/Dimension");
    player2_score = bot_host_register_property_fail(host,
            PLAYER_2 "/Client/Score");

    // grab Player1's ball
    ball_pos = bot_host_register_property_fail(host, PLAYER_1 "/Ball/Position");

    // work out the screen size
    ball_range = bot_host_register_property_fail(host, PLAYER_1 "/Ball/Range");
    bot_property_get_uint_all(ball_range, range);

    // set bat position for player 1
    if (player1) {
        bot_property_get_uint_all(player1_dim, dim);
        p1_init_pos[0] = 0;
        p1_init_pos[1] = (range[1] / 2) - dim[1];
        bot_property_set_uint_all(player1_pos, p1_init_pos);
    }

    // set bat position for player 2
    if (player2) {
        bot_property_get_uint_all(player2_dim, dim);
        p2_init_pos[0] = range[0] - dim[0];
        p2_init_pos[1] = (range[1] / 2) - dim[1];
        bot_property_set_uint_all(player2_pos, p2_init_pos);
    }

    // initialize the video, et al. title is set on player
    init_pong(player1);

    // ensure updates are drawn only when necessary (synced to the ball moving)
    bot_property_add_callback(ball_pos, NULL, update_pong, CALLBACK_RELAXED);

    // different keyboard handlers for different players (this blocks)
    if (player1) {
        sdl_keyboard_handler(up_pressed, down_pressed, 1);
    } else {
        sdl_keyboard_handler(up_pressed, down_pressed, 2);
    }

    // stop drawing updates
    bot_property_remove_callback(ball_pos, NULL, update_pong);

exit_out:
    // stop the host
    if (host)
        bot_host_stop(host);

    // clean up after SDL
    shutdown_pong();
    return 0;
}

Third-Party Libraries

This tutorial makes use of the Simple Directmedia Layer and SDL Truetype Font Library and SDL Image Library.

These components are released under the GNU Lesser General Public License.

Previous tutorial Next tutorial