The Source for Java Technology Collaboration
User: Password:



Kohsuke Kawaguchi

Kohsuke Kawaguchi's Blog

Hangman game by using Dalma

Posted by kohsuke on October 22, 2005 at 12:02 PM | Comments (3)

Dalma is a workflow engine that lets you write conversational programs quickly. In my last blog about Dalma, I showed a little code snipet that explains the concept, but I wanted to have the real working code. So today, I added a little hangman game as a sample to Dalma. It's a daemon that handles hangman games with multiple users concurrently, via e-mail.

The entry point looks like this:

public class Main {
    public static void main(String[] args) throws Exception {

        ...

        // initialize the directory to which we store data
        File root = new File("hangman-data");

        // set up an engine.
        // we'll create one e-mail endpoint from the command-line.
        final Engine engine = EngineFactory.newEngine(root,new ThreadPoolExecutor(1,true));
        final EmailEndPoint eep = (EmailEndPoint)engine.addEndPoint("email", args[0]);
        eep.setNewMailHandler(new NewMailHandler() {
            /**
             * This method is invoked every time this endpoint receives a new e-mail.
             * Start a new game.
             */
            public void onNewMail(MimeMessage mail) throws Exception {
                System.out.println("Starting a new game for "+mail.getFrom()[0]);
                engine.createConversation(new HangmanWorkflow(eep,mail));
            }
        });

        // start an engine
        engine.start();
        System.out.println("engine started and ready for action");
    }
}

Basically it sets up an engine with an e-mail connectivity, and whenever a "new" e-mail is received, it creates a new HangmanWorkflow instance and starts it as a conversation. (E-mails that are replies to existing conversations will be delivered to their waitForReply method.) HangmanWorkflow class looks like this:

public class HangmanWorkflow implements Runnable, Serializable {
    /**
     * {@link EndPoint} that we are talking to.
     */
    private final EmailEndPoint ep;
    private final MimeMessage msg; // the first message

    public HangmanWorkflow(EmailEndPoint ep, MimeMessage msg) {
        this.ep = ep;
        this.msg = msg;
    }

    public void run() {
        // the answer
        String word = WordList.getRandomWord();

        int retry = 6;  // allow 6 guesses

        // the characters the user chose
        // true means selected
        boolean[] opened = new boolean[26];

        MimeMessage mail = msg; // last e-mail received

        while(retry>0) {
            // send the hint
            mail = (MimeMessage)mail.reply(false);
            mail.setText(
                "Word: "+maskWord(word,opened)+"\n\n" +
                "You Chose: "+maskWord("abcdefghijklmnopqrstuvwxyz",opened)+"\n\n"+
                retry+" more guesses\n");

            mail = ep.waitForReply(mail);

            System.out.println("Received a reply from "+mail.getFrom()[0]);

            // pick up the char the user chose
            char ch =getSelectedChar(mail.getContent());
            if(ch==0)
                continue;

            if(word.indexOf(ch)<0) {
                // bzzzt!
                retry--;
            }

            opened[ch-'a']=true;

            if(maskWord(word,opened).equals(word)) {
                // bingo!
                mail = (MimeMessage)mail.reply(false);
                mail.setText("Bingo! The word was\n\n   "+word);
                ep.send(mail);
                return;
            }
        }

        MimeMessage reply = (MimeMessage)mail.reply(false);
        reply.setText("Bzzzt! The word was\n\n   "+word);
        ep.send(reply);
    }
}

As you see, all the conversational state is in the local variables, and there's no code that handles persistence explicitly (other than the "implements Serializable") This code is run as a conversation, meaning whenever the following line is invoked:

            mail = ep.waitForReply(mail);

the state is persisted into a disk, and the Java thread that runs it is reused to run other conversations. The program is not event-driven at all, and I'm hoping that this brings the same productivity gain that the StAX API brought to Java compared to SAX.

This sample does bytecode instrumentation as a part of the build process to make this magic happen.

I deployed it at hangman at kohsuke dot org, so you can write an e-mail to this address to start a new hangman game. You can write multiple e-mails to run mutliple games in parallel (each on-going game has one HangmanWorkflow instance.) See this page for how to play the game.

To show that it's actually persisting state, the daemon is killed every half an hour and restarted half an hour later. You'll see this as a delay in the e-mail reply from daemons, but other than that, you'll see that the JVM shutdown is transparent to the game you are running.

See this page for more about this sample, and this sample is a part of the distribution, so you can run it locally if you want.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Nice! Just one question: what happens if the player doesn't finish the game?

    Posted by: tobega on October 24, 2005 at 02:31 AM

  • Good point. I'll modify the code to time out after 1 week.
    I realized that it might be better if the expiration date is set on a Conversation object. It's probably easier that way, as such expiration date would be effective regardless of what a conversation is currently blocked on. Thanks for an insightful question.

    Posted by: kohsuke on October 25, 2005 at 09:56 PM

  • Just had an idea. Could you use dalma as a conversation orchestrator, where neither of the parties logic is directly connected?

    Posted by: tobega on October 31, 2005 at 02:33 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds