Click here to Skip to main content
Click here to Skip to main content

Use Twiggery as JavaME Game Scripting Language

, 30 Aug 2010 LGPL3
Rate this:
Please Sign up or sign in to vote.
Use Twiggery as JavaME Game Scripting Language

Introduction

Nowadays mobile development seems to be more popular than years ago. I've dived into it and have been paying interest since my college time. I found many applications on PC powered by scripting languages (such as Lua, Python, Ruby, etc.) show great flexibility and dynamic behavior, but almost nothing could take a similar role as those scripts on mobile platforms without fast CPU, bulk RAM or even enough battery capacity. So that is why my Twiggery scripting language is created.

Twiggery is not omnipotent, but domain specific. You can get more information about syntax of Twiggery and download the full package here.

This article will show how to write a host application with Twiggery scripting interface, how to code and compile Twiggery source into bytecode using those interfaces, how to embed bytecode in a host, and finally how to deploy a solution application powered by Twiggery. I'll make a full Tic-Tac-Toe game with rule checking and AI simulation as an example.

Background

I suppose you have known some fundamentals about JavaME programming before, but it doesn't matter if you haven't because I won't mention idioms of JavaME in this article. You'll come to the point.

Java, C# and C++ based TVM (Twiggery Virtual Machine) are ready-made in the standard Twiggery package. It’s incredible that you can power up your applications just after inheriting from a TVM implementation, isn't it? It’s also very easy to code another imitation in hundreds of lines with other programming languages such as Objective-C, ActionScript, Python, etc.

Using the Code

Let’s code a full Tic-Tac-Toe game in four steps. Of course, we can construct a game without any scripting language, then what I'm wordy here for, the answer is for flexibility. I'll get easy on that, you'd find it more necessary to use Twiggery if you were creating a more complex game with features such as “Optional dialog”, "Quest”, "Switch”, etc.

Step 1. Building a Basic Game Shell

You should know that script runs slower than its native host programming language. This means scripting languages are more suitable for states controlling than intensive computation. So let’s build up a game shell with Java, we would first do program initialization, key input checking, rendering, etc.

A game always comes with a main loop. I would do it like this:

/**
* Main game loop
*/
public void run() {
    int timeStep = 50;
    while (true) {
        long start = System.currentTimeMillis();
        try {
            if (!canvas.render()) { // Looping until user exits
                destroyApp(true);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        long end = System.currentTimeMillis();
        int duration = (int) (end - start);
        int d = timeStep - duration;
        if (d > 0) { // Delay?
            try {
                Thread.sleep(d);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
}

And I would start that looping as a thread in JavaME like this:

private TicTacToe canvas = null;
/**
* Entry point
*/
public void startApp() {
    canvas = new TicTacToe();
    Display.getDisplay(this).setCurrent(canvas);
    Thread thread = new Thread(this);
    thread.start();
}

A key input processing callback method is called automatically. We would deal with cursor movement and chessmen placement in it like this:

/**
* Process key events
* @param keyCode The key code got here
*/
protected void keyPressed(int keyCode) {
    int keyState = getGameAction(keyCode);
    int winner = logic.getWinner();
    if (Logic.EMPTY == winner) { // Nobody wins
        if (GameCanvas.FIRE == keyState) {
            if (logic.getCell(cursorX, cursorY) == Logic.EMPTY) {
                // Place a chessman of yours
                logic.setCell(cursorX, cursorY, logic.getHolder());
                logic.rule();
                logic.turn();
                // Not win?
                if (Logic.EMPTY == logic.getWinner()) {
                    // Place a chessman of AI automatically
                    logic.ai();
                    logic.rule();
                    logic.turn();
                }
            }
        } else {
            // Process arrow keys
// Make cursor movement
        }
    } else { // Somebody wins
        // Press fire key to continue
        if (GameCanvas.FIRE == keyState) {
            logic.reset();
        }
    }
}

Notice that calling logic.ai() and logic.rule() performs script execution; they are blank now but filled later.

The core rendering method would be like this:

/**
* Render all
* @return Keep on running if true, otherwise exit the game
*/
public boolean render() throws Exception {
    // Clear scene
    graphics.setColor(0xffffffff);
    graphics.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    {
        // Render all
// Render cell grids
// Render chessmen
// Render cursor or information
// Render "Exit" text
        …
    }
    // Flush!
    flushGraphics(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    return running;
}

Here are some final game screens, let’s take a look at them first:

Step 2. Coding Script Programming Interfaces

After the game shell is done, we should make some script programming interfaces as well. This part is done with the host programming language and works as a glue which agglutinates the game shell and script together.

Twiggery allows running compiled bytecode on a virtual machine, it goes with some advantages:

  1. Only loading and just executing it, no compiling time cost
  2. Less memory occupation
  3. Script source protection
  4. Portability

The first two points are particularly important for mobile devices.

There would be two kinds of interfaces, one is accessing from host program to script, and the other one is the opposite way.

From Host to Script

A TVM provides initialization, loading and executing methods, and what we need to do is just put them together into a function like this if we want to call a compiled script:

/**
* Call a rule script to check current composition
*/
public void rule() {
    try {
        String scriptFile = "/rule.tad"; // Compiled bytecode file
        loadAsm(scriptFile);
        runAsm();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}
/**
* Call a AI script to make a automatic move
*/
public void ai() {
    try {
        String scriptFile = "/ai.tad"; // Compiled bytecode file
        loadAsm(scriptFile);
        runAsm();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

You can call the rule and AI script execution methods like logic.rule(), logic.ai() as it appeared in step 1.

From Script to Host

We'd like to expose some programming interfaces to Twiggery so that we could control the game shell via script.

An interfaces list maybe like this:

/**
* Make a function call
* Overridden from TVM
* @param funName Remote function name
* @param argCount Arguments count
*/
protected void call(String funName, int argCount) throws Exception {
    if (funName.equals("abs")) {
        abs();
    } else if (funName.equals("int")) {
        _int();
    } else if (funName.equals("rnd")) {
        rnd();
    } else if (funName.equals("array_new")) {
        array_new();
    } else if (funName.equals("array_get")) {
        array_get();
    } else if (funName.equals("array_set")) {
        array_set();
    } else if (funName.equals("getCell")) {
        getCell();
    } else if (funName.equals("setCell")) {
        setCell();
    } else if (funName.equals("gameOver")) {
        gameOver();
    } else {
        throw new Exception("Unknown function call");
    }
}
/**
* Initialize a new array
*/
private void array_new() { … }
/**
* Get a value from a array unit
*/
private void array_get() { … }
/**
* Set a value to a array unit
*/
private void array_set() { … }
/**
* Get the absolute value of a number
*/
private void abs() { … }
/**
* Floor
*/
private void _int() { … }
/**
* Get a random number
*/
private void rnd() { … }
/**
* Get the state of a cell
*/
private void getCell() { … }
/**
* Set the state of a cell
*/
private void setCell() { … }
/**
* Set game over
*/
private void gameOver() { … }

Notice the “call” method is overridden from base class TVM. This method would be called to decide which interface should be executed when running a function call evaluation from script.

/**
* Get the state of a cell
* Args:
* 1) X index of a cell
* 2) Y index of a cell
* Return:
* A constant among WHITE, BLACK, EMPTY
*/
private void getCell() throws Exception {
    int y = (int) popArgument();
    int x = (int) popArgument();
    int v = cells[x][y];
    returnArgument((float) v);
}

Arguments are pushed from left to right, and that means they are popped from right to left. Like above, when we call the “getCell” function from Twiggery such as: c = getCell(1, 2) we are pushing 1 then 2 into a TVM stack, and we have to pop the second argument out then the first one (2 to y, 1 to x here). Finally, we could return some value to “c” by calling “returnArgument”.

Step 3. Coding & Compiling Game Scripts

In this part, we would write some Twiggery code to construct the rule checking and the AI script using those scripting interfaces. Actually step 2 and step 3 are cyclic iterative, sometimes we can't figure out how many scripting interfaces should be done before writing any script. When we need to use an interface which have not been created, we may go back to step 2 and make a new one.

Rule

In this tic-tac-toe game, I would check who wins the game in a Twiggery script like this:

function rule() { ' Twiggery code
    ' Consts
    WHITE = 1;
    BLACK = -1;
    EMPTY = 0;
    TIE = 2;
    ' Check diagonal lines
    diagonal_0 = getCell(0, 0) + getCell(1, 1) + getCell(2, 2);
    diagonal_1 = getCell(2, 0) + getCell(1, 1) + getCell(0, 2);
    abs_diagonal_0 = abs(diagonal_0);
    abs_diagonal_1 = abs(diagonal_1);
    c = 0;
    if(abs_diagonal_0 == 3) {
        c = diagonal_0;
    } elseif(abs_diagonal_1 == 3) {
        c = diagonal_1;
    }
    if(c > 0) {
        gameOver(WHITE);
        return;
    } elseif(c < 0) {
        gameOver(BLACK);
        return;
    }
    ' Check columns and rows
    for(j = 0 to 2) {
        l = 0;
        r = 0;
        for(i = 0 to 2) {
            l = l + getCell(i, j);
            r = r + getCell(j, i);
        }
        al = abs(l);
        ar = abs(r);
        c = 0;
        if(al == 3) {
            c = l;
        } elseif(ar == 3) {
            c = r;
        }
        if(c > 0) {
            gameOver(WHITE);
            return;
        } elseif(c < 0) {
            gameOver(BLACK);
            return;
        }
    }
    ' Tie
    c = 0;
    for(i = 0 to 2) {
        for(j = 0 to 2) {
            t = getCell(i, j);
            if(t ~= 0) {
                c = c + 1;
            }
        }
    }
    if(c == 9) {
        gameOver(TIE);
    }
}

AI

And let’s make an AI:

function ai() { ' Twiggery code
    ' Consts
    WHITE = 1;
    BLACK = -1;
    EMPTY = 0;
    TIE = 2;
    ' Initialize a array
    array_new(8);
    ' Rows counting
    i = getCell(0, 0) + getCell(1, 0) + getCell(2, 0);
    array_set(0, i);
    i = getCell(0, 1) + getCell(1, 1) + getCell(2, 1);
    array_set(1, i);
    i = getCell(0, 2) + getCell(1, 2) + getCell(2, 2);
    array_set(2, i);
    ' Columns counting
    i = getCell(0, 0) + getCell(0, 1) + getCell(0, 2);
    array_set(3, i);
    i = getCell(1, 0) + getCell(1, 1) + getCell(1, 2);
    array_set(4, i);
    i = getCell(2, 0) + getCell(2, 1) + getCell(2, 2);
    array_set(5, i);
    ' Diagonal lines counting
    i = getCell(0, 0) + getCell(1, 1) + getCell(2, 2);
    array_set(6, i);
    i = getCell(2, 0) + getCell(1, 1) + getCell(0, 2);
    array_set(7, i);
    ' Search
    found = false;
    for(c = 2 to 1 step -1) {
        for(i = 0 to 7) {
            t = array_get(i);
            t = abs(t);
            if(t == c) {
                found = true;
                break;
            }
        }
        if(found) {
            break;
        }
    }
    ' Found a target cell
    if(found) {
        if(i >= 0 and i <= 2) { ' Rows
            for(j = 0 to 2) {
                t = getCell(j, i);
                if(t == 0) {
                    setCell(j, i, BLACK);
                    return;
                }
            }
        } elseif(i >= 3 and i <= 5) { ' Columns
            k = i - 3;
            for(j = 0 to 2) {
                t = getCell(k, j);
                if(t == 0) {
                    setCell(k, j, BLACK);
                    return;
                }
            }
        } elseif(i == 6) { ' Diagonal \
            for(j = 0 to 2) {
                t = getCell(j, j);
                if(t == 0) {
                    setCell(j, j, BLACK);
                    return;
                }
            }
        } elseif(i == 7) { ' Diagonal /
            for(j = 0 to 2) {
                k = 2 - j;
                t = getCell(k, j);
                if(t == 0) {
                    setCell(k, j, BLACK);
                    return;
                }
            }
        }
    }
    ' Did not find any cell, choose one randomly
    c = 0;
    for(i = 0 to 2) {
        for(j = 0 to 2) {
            t = getCell(i, j);
            if(t == 0) {
                k = i + j * 3;
                array_set(c, k);
                c = c + 1;
            }
        }
    }
    r = rnd(0, c);
    k = array_get(r);
    i = k % 3;
    j = k / 3;
    j = int(j);
    setCell(i, j, BLACK);
}

Now we may compile these script source files into bytecode with CodeLeaf (supplied in the Twiggery development package) and put them in an accessible directory for our game shell.

Step 4. Making a Publishable Pack

You can deal with compiled Twiggery script similar to other types of data files.

When developing a JavaME application, I always put all resources (images, sound, data files) together with compiled *.class files in a single *.jar package, and I prefer doing the same with Twiggery bytecode even though I could put them in some other directories separated from the *.jar package. If you are not working on JavaME but some other platforms, I recommend you to do that in a corresponding regular way on those platforms.

Conclusion

It’s my pleasure to introduce to you such a mobile development aimed scripting language, I hope it would be helpful. I invite you to post your questions, suggestions and ideas below. I’ll do my best to make this better! Thank you for reading!

History

  • Aug. 30, 2010: Fixed some spelling mistakes
  • Aug. 27, 2010: First description

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

paladin_t
Architect
China China
Video game player & creator.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 30 Aug 2010
Article Copyright 2010 by paladin_t
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid