Skip to main content

Hangman game by using Dalma

Posted by kohsuke on October 22, 2005 at 12:02 PM PDT

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 {<br />    /**<br />     * {@link EndPoint} that we are talking to.<br />     */<br />    private final EmailEndPoint ep;<br />    private final MimeMessage msg; // the first message<br /><br />    public HangmanWorkflow(EmailEndPoint ep, MimeMessage msg) {<br />        this.ep = ep;<br />        this.msg = msg;<br />    }<br /><br />    public void run() {<br />        // the answer<br />        String word = WordList.getRandomWord();<br /><br />        int retry = 6;  // allow 6 guesses<br /><br />        // the characters the user chose<br />        // true means selected<br />        boolean[] opened = new boolean[26];<br /><br />        MimeMessage mail = msg; // last e-mail received<br /><br />        while(retry>0) {<br />            // send the hint<br />            mail = (MimeMessage)mail.reply(false);<br />            mail.setText(<br />                "Word: "+maskWord(word,opened)+"\n\n" +<br />                "You Chose: "+maskWord("abcdefghijklmnopqrstuvwxyz",opened)+"\n\n"+<br />                retry+" more guesses\n");<br /><br />            mail = ep.waitForReply(mail);<br /><br />            System.out.println("Received a reply from "+mail.getFrom()[0]);<br /><br />            // pick up the char the user chose<br />            char ch =getSelectedChar(mail.getContent());<br />            if(ch==0)<br />                continue;<br /><br />            if(word.indexOf(ch)<0) {<br />                // bzzzt!<br />                retry--;<br />            }<br /><br />            opened[ch-'a']=true;<br /><br />            if(maskWord(word,opened).equals(word)) {<br />                // bingo!<br />                mail = (MimeMessage)mail.reply(false);<br />                mail.setText("Bingo! The word was\n\n   "+word);<br />                ep.send(mail);<br />                return;<br />            }<br />        }<br /><br />        MimeMessage reply = (MimeMessage)mail.reply(false);<br />        reply.setText("Bzzzt! The word was\n\n   "+word);<br />        ep.send(reply);<br />    }<br />}<br />

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.

Related Topics >>