Friday, December 7, 2007

My first AI... Very "A", not "I" at all

My first Artificial Intelligence ever is ready, I don't think it qualifies as an AI since it doesn't learn, it's just a very basic "move evaluation and selection algorithm", but aren't chess engines the same thing??? Of course !!! you don't install your newly acquired ChessMaster and then "train" it submitting hundreds of thousands of highly selected games so it learns what is a good move and what is not.

Why do we use the term Artificial Intelligence? I have not clue, so I thought, should I use "human vs phone" instead? I'm still thinking about it, but for the time being, I'm going with the flow of the industry and I have labeled it AI.

What's even worst, as can be seen from the following code, my move evaluation and selection algorithm is not even that :(. It doesn't "evaluate" the possible moves and then "select" the best option. It just finds the first possible valid move and does it. It's very gratifying to watch the game answer your moves though. Even if the moves it does are absolutely dumb. I hope I can proudly label AI the second level when it's done, the first level should be labeled Artificial Stupidity...


public byte[][] getMoves(Board board)
{
byte moves[][] = new byte[board.getAvailableMovesCount()][2];
super.cloneBoard(board);
int currentMove = 0;
for(int i = 0; i <>
{
if(super.availMoves[i] != 0)
System.out.print(super.availMoves[i] + " ");
System.out.println();
}

label0:
do
{
if(currentMove <>
{
if(super.barCount[board.getTurn() - 1] > 0)
{
for(int i = 0; i <>
{
if(super.availMoves[i] == 0)
continue;
int point = board.getTurn() != 2 ? 24 - super.availMoves[i] : super.availMoves[i] - 1;
if(super.boardColor[point] == board.getOpponent() && super.boardCount[point] >= 2)
continue;
moves[currentMove][0] = -1;
moves[currentMove][1] = (byte)point;
System.out.println("moving from " + moves[currentMove][0] + " to " + moves[currentMove][1]);
super.barCount[board.getTurn() - 1]--;
if(super.boardColor[point] != board.getTurn())
super.boardCount[point] = 1;
else
super.boardCount[point]++;
super.boardColor[point] = board.getTurn();
currentMove++;
super.availMoves[i] = 0;
continue label0;
}

return moves;
}
int j;
if(board.playerCanBearOff())
label1:
for(j = 6; j > 0; j--)
{
int point = board.getTurn() != 2 ? j - 1 : 24 - j;
if(super.boardColor[point] != board.getTurn())
continue;
int i = 0;
do
{
if(i >= 4)
continue label1;
if(super.availMoves[i] > j)
{
moves[currentMove][0] = (byte)point;
Board _tmp = board;
moves[currentMove][1] = -2;
super.boardCount[point]--;
if(super.boardCount[point] == 0)
super.boardColor[point] = 0;
currentMove++;
super.availMoves[i] = 0;
continue label0;
}
i++;
} while(true);
}

j = board.getTurn() != 2 ? 23 : 0;
do
{
if(board.getTurn() != 2 ? j <>= 24)
continue label0;
if(super.boardColor[j] == board.getTurn())
{
for(int i = 0; i <>
{
if(super.availMoves[i] == 0)
continue;
int point = board.getTurn() != 2 ? j - super.availMoves[i] : j + super.availMoves[i];
if(point <>= 24 super.boardColor[point] == board.getOpponent() && super.boardCount[point] >= 2)
continue;
moves[currentMove][0] = (byte)j;
moves[currentMove][1] = (byte)point;
System.out.println("moving from " + moves[currentMove][0] + " to " + moves[currentMove][1]);
super.boardCount[j]--;
if(super.boardCount[j] == 0)
super.boardColor[j] = 0;
if(super.boardColor[point] == board.getOpponent())
super.boardCount[point] = 1;
else
super.boardCount[point]++;
super.boardColor[point] = board.getTurn();
currentMove++;
super.availMoves[i] = 0;
continue label0;
}

}
j += board.getTurn() != 2 ? -1 : 1;
} while(true);
}
return moves;
} while(true);
}


P.D.: Even though the first level of the AI is absolutely stupid, it has managed to beat a couple of friends once or twice. Obviously they hadn't played the game before. So the skill level is not that inappropriate after all.

The importance of beta testing

Of course you can test your program yourself, you run it a thousand times when you're developing it testing small pieces of the code. But when you want to develop a first-class program without spending a lot of money, you need to ask some friends for their help in this annoying but very important task.

It doesn't matter if you offer them a free copy of the final release, a couple beers, a free dinner (specially good idea if the beta tester in question is a girl you like), you will always find at least half a dozen friends willing to test your game. This will allow you to have different points of view regarding certain features and will expose your program to different environments. In this case, testing your mobile game against different phones is very important because they all use different screen resolutions and keyboard mappings.

As of today, I haven't really figured out the best way to tackle those two problems: screen resolution and keyboard mappings.

For the first one, I've made everything that's drawn in the screen adjust to a ratio of the total screen resolution, but that's more difficult when it comes to stored graphics because you need to choose a different image file depending on the resolution. It has come to my mind that I could make a "screen definition" file format that defines which image files to use and where to put them in the screen. It could also define font styles and sizes, colors and so on. This would not only make it very easy to setup a new screen resolution for a newly marketed mobile device but would also make the game "skinable", which would make a great feature altogether.

About the keyboard mappings, I also think I should make a "keyboard configuration" option. That way, the user itself could choose what keys he wants to use for every action and the game would work with every possible device.

As a proof of concept, and also to figure out the keyboard mappings of the phones a couple friends use, I developed a small Midlet that gives feedback of the keys as the user presses them. This is the code of the Canvas that does it all:

import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.io.IOException;
import java.io.InputStream;

class KeyTestCanvas extends Canvas {

private int lastKeyCode;

public KeyTestCanvas(){
}

protected void paint(Graphics g){
int w = getWidth();
int h = getHeight();
g.setColor(0x000000);
g.fillRect(0, 0, w, h);
g.setColor(0xFFFFFF);
g.drawString("keyCode:", 10,10,Graphics.TOP|Graphics.LEFT);
g.drawString(Integer.toString(lastKeyCode), 80,10,Graphics.TOP|Graphics.LEFT);
g.drawString("GameAction:", 10,30,Graphics.TOP|Graphics.LEFT);
try {
g.drawString(Integer.toString(getGameAction(lastKeyCode)), 80,30,Graphics.TOP|Graphics.LEFT);
} catch (Exception e) {
g.drawString("Exception", 80,30,Graphics.TOP|Graphics.LEFT);
}
g.drawString("KeyName:", 10,50,Graphics.TOP|Graphics.LEFT);
try {
g.drawString(getKeyName(lastKeyCode), 80,50,Graphics.TOP|Graphics.LEFT);
} catch (Exception e) {
g.drawString("Exception", 80,50,Graphics.TOP|Graphics.LEFT);
}
}

protected void keyPressed(int keyCode){
lastKeyCode = keyCode;
repaint();
}

}