Table of ContentsThe Server SideAdvanced Networking

Online Scoring for Star Assault

Now that you've covered all the basics of MIDlet networking, you can put it all to some practical use and add online scoring to Star Assault. The plan is pretty simpleyou'll add some simple scoring (based on how many ships the player kills) and then after each game, you'll give the player the opportunity to submit his score online. Your servlet will then update a global rankings table and return a ranking order to the player.

Basic Scoring

The first thing you need to do is add scoring to the basic Star Assault game. To do this, you first need to track the score using an integer in the GameScreen class. For example:

NOTE

Tip

You can see the complete online scoring system in the final Star Assault project in the source code directory in the CD.

public class GameScreen extends Canvas implements Runnable, CommandListener
{
    private int score;
    public void incScore(int i)
    {
        score += i;
    }

To register a score you modify the onCollision method of the Ship class to call the incScore method whenever an enemy ship dies a horrible death.

public class Ship extends Actor
{
    public final void onCollision(Actor a)
    {
        ...

        if (this.getType() != PLAYER_SHIP)
        {
            ...

            // add to the score
            GameScreen.getGameScreen().incScore(100);
        }
    }
    ...

You could get a lot fancier with scoring here (such as giving different points for the various types of enemies), but this works for this example.

Next you need to update the GameScreen class again to render the score to the player. You can do that by modifying the renderWorld method to draw the score in the top corner of the screen.

private final void renderWorld(Graphics graphics)
{
    ...

    graphics.setColor(0x00ffffff);
    graphics.setFont(defaultFont);
    graphics.drawString(""+score, getWidth()-2, 2, Graphics.TOP|Graphics.RIGHT);

That's all you need to add basic scoring to Star Assault. Next you'll add the online part of your scoring system.

The Online Scoring Class

To implement your online scoring system, you need to add a class that presents the user with a screen to submit and then check his online ranking. As we saw in Chapter 5, "The J2ME API," you can create a new display class by extending the LCDUI Form. The OnlineScoring class shown in the following code presents the user's score to him with the option to either cancel or transmit. If the player hits the send command, you then create a thread for communications, and it in turn calls the transmitScore method. Once a rank string is returned, you present it to the user using the same form.

import javax.microedition.lcdui.*;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.Connector;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class OnlineScoring extends Form implements CommandListener
{
    private int score;
    private Command cancel;
    private Command send;
    private Command ok;

    private HttpConnection httpConnection;

The constructor sets up a basic Form to transmit and display the score. All the action starts in response to the "Send" command.

    public OnlineScoring(int scoreArg)
    {
        super("Online Score");
        score = scoreArg;

        append("Transmit score of " + score + "?");

        send = new Command("Send", Command.OK, 1);
        addCommand(send);
        cancel = new Command("Cancel", Command.CANCEL, 2);
        addCommand(cancel);

        setCommandListener(this);
    }

    public void showResult(String s)
    {
        append(s);
    }

When the "Send" command is executed this handler takes care of creating a thread and transmitting the score.

    public void commandAction(Command c, Displayable s)
    {
        if (c == send)
        {
            // get rid of the send command from the form
            removeCommand(send);

            // create a new thread for the networking

This is a neat trick if you haven't seen it before. The Thread class is declared inline in one statement. This saves having to create another specific class for the run method.

    Thread t = new Thread()
    {
        // declare a run method (not called until the thread is started
        // (see t.start below)
        public void run()
        {
            // send score to the server and retrieve the resulting rank
            // WARNING: this method will STALL until we get a response
            // or it times out
            String result = transmitScore();

Once you have a result, the current display text is removed and the result is shown.

            // delete the current text item
            delete(0);
            // present the result to the player
            showResult(result);
            // get rid of the cancel command and add an OK
            removeCommand(cancel);
            ok = new Command("OK", Command.OK, 1);
            addCommand(ok);
        }
    };
    // start the thread (execute the contents of the run method above)
        t.start();
    }
    if (c == ok || c == cancel)
        StarAssault.getApp().activateMenu();
}

This is the method that takes care of all the communications to transmit the score to the server. Once a result has been received it returns it as a String.

public String transmitScore()
{
    InputStream in = null;
    OutputStream out = null;

    try
    {
        // submit score to server and read ranking response
        httpConnection = (HttpConnection) Connector.open("http://localhost:8080/
        StarAssault/updateScore");

        // close the connection after req/response (don't bother keeping it alive)
        httpConnection.setRequestProperty("Connection", "close");

        // set up for a post
        httpConnection.setRequestMethod(httpConnection.POST);
        String req = "" + score;
        httpConnection.setRequestProperty("Content-Length",
        Integer.toString(req.length()));

        // output the request
        out = httpConnection.openOutputStream();
        for (int i = 0; i < req.length(); i++)
            out.write(req.charAt(i));

        // read the result
        in = httpConnection.openInputStream();

        if (httpConnection.getResponseCode() == httpConnection.HTTP_OK)
        {
            int contentLength = (int) httpConnection.getLength();
            if (contentLength == -1) contentLength = 255;
            StringBuffer response = new StringBuffer(contentLength);
                for (int i = 0; i < contentLength; i++)
                    response.append((char) in.read());

                String rankString = response.toString();
                return "You are currently ranked " + rankString + ".";
            }
            else
                throw new IOException();
        }

        catch (IOException e)
        {
            return "Error transmitting score.";
        }

        finally
        {
            if (httpConnection != null)
            {
                try
                {
                    httpConnection.close();
                }

                catch (IOException ioe)
                {
                }
            }

            if (in != null)
            {
                try
                {
                    in.close();
                }

                catch (IOException ioe)
                {
                }
            }

            if (out != null)
            {
                try
                {
                    // clean up
                    out.close();
                }
                catch (IOException ioe)
                {
                }
            }
        }
    }
}

The transmitScore method in this code calls the URL http://localhost:8080/StarAssault/updateScore to submit the score to the server. You'll look at the corresponding servlet for this in the next section.

The Scoring Servlet

The scoring servlet implements the server-side component of your little system. Upon receiving a request (via a POST) you read the score, update a vector of all the scores, and then send back the player's ranking.

import java.io.*;
import java.util.Vector;
import java.util.Collections;
import javax.servlet.*;
import javax.servlet.http.*;

public class ScoreServlet extends HttpServlet
{
    private static Vector topScores = new Vector();

    public void doPost(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException
    {
        // open up the input stream and convert it into a string
        InputStream in = req.getInputStream();
        int requestLength = req.getContentLength();
        if (requestLength < 1) throw new IOException("invalid request length");
        StringBuffer sb = new StringBuffer(requestLength);
        for (int i = 0; i < requestLength; i++)
        {
            int c = in.read();
            if (c == -1)
                break;
            else
                sb.append((char) c);
        }
        in.close();
        String rs = sb.toString();

        // process things
        System.out.println("New score uploaded: " + rs);

        int newScore = Integer.parseInt(rs);
        Integer newEntry = new Integer(newScore);
        topScores.add(newEntry);
        Collections.sort(topScores);
        int rank = topScores.indexOf(newEntry) + 1;
        String rankString = rankedInt(rank);
        System.out.println("Rank: " + rankString);

        // send back a response
        res.setContentType("text/plain");
        PrintWriter out = res.getWriter();
        out.write(rankString);
        out.close();
    }

    static public String rankedInt(int i)
    {
        String result = "";

        String n = "" + i;
        int lastNum = Integer.parseInt("" + n.charAt(n.length() - 1));
        int lastTwoNums = 0;
        if (n.length() > 1)
            lastTwoNums = Integer.parseInt("" + n.charAt(n.length() - 2) +
n.charAt(n.length() - 1));

        if (lastNum >= 1 && lastNum <= 3)
        {
            if (lastTwoNums < 10 || lastTwoNums > 13)
            {
                if (lastNum == 1) result = "st";
                if (lastNum == 2) result = "nd";
                if (lastNum == 3) result = "rd";
            }
        }
        else
            result = "th";
        return "" + i + result;
    }
}

To now trigger the online process, you need to add code to the GameScreen class when it hits the game over state. For example:

    public void run()
    {
        try
        {
            while (running)
            {
                ...

                if (state == GAME_OVER)
                {
                    long timeSinceStateChange = System.currentTimeMillis() -
                            timeStateChanged;
                    if (timeSinceStateChange > 3000)
                    {
                        setState(GAME_DONE);
                        StarAssault.getApp().activateDisplayable(
                                new OnlineScoring(score));
                    }
                }

                ...

That's it for your simple online scoring system. To make it work for a production game I'd do a few more things, such as saving the score to RMS and asking for the player's name. I'd also provide a screen the player could use to look up his current ranking at any time. The server side would also need to save the current rankings list to persistent storage, such as a database, since the current version will lose all the scores whenever you restart Tomcat (see the section "Server-Side Persistence" later in the chapter for more information).

    Table of ContentsThe Server SideAdvanced Networking