AI-general

AI in JPortal

I don't know how much you have read in the available documentation of the JPortal. If you have, you already might have a basic idea how I implemented AI.

For the sake of all who haven't I'll shortly draw a picture how AI is handled. (This information might be redundant to what is written in the available game documentation)

To begin I recall the scripting mechanism I used.

Bean Shell

This is REALY easy. You get the jars and call an interpreter class for a given script. You can pass environment information to the interpreter environment via easy "set" calls. For JPortal I pass (in general) the whole game to the interpreter environment. Which in fact means that the script can access ALL information availble and actually call any methods that one could also call in "hardoded" Java code. There virtually is NO DIFFERENCE between interpreted code and Java "compiled" classes. With that in mind you can wrtite your scripts like they were just parts of the main program. This is what is actually done!

Communication between game and players

  1. The main game is ignorant whether a player is human or a computer AI. All communication is done via message passing.

  2. All kind of players must implement the interface "MatchPlayable" (only about 10 methods)

The AI is called to handle "situations". Situations can be divided into two major categories

  1. answer a concrete "call"

  2. do something that is appropriate

"Concrete calls"

This usually means something is happening and the player is asked to make a decision. Like chosing a target of some kind, draw a card from the library, destroy a target etc. For each possible combination available I have so called "situation keys". Within the player code there is something like a "switch case" which handles the situation keys. Depending on the situation key, a special code section is called. (I also implemented a "general" situation, which "could" handle all calls - so no switching would be needed in the code, but than there would be some decision making in the AI code - which really amounts to the same... in the end).

What it comes down to is - that for a special situation key a special script is called, which handles the situation. Since the script more or less knows everything from it's environment it can react appropriatly. (Scripts for the "allround" AI are kept withing the "./scripts/Allround/" folder)

An example.
Card "Angelic Blessing" - +3 / +3 Flying The player can chose a target. There is no specification on the card what kind of target. Even an enemy unit can be selected. Within the card code there is a call: match.askForCreatureFromCreature(player, card, ev);

Which basically means:

The match initiates communication with player "player" (which is same one who just played the card) if it is a computer player and allround AI is the AI than the script:
".\scripts\Allround\ComSelectTargetCreature.java"

Is called. This is a communication call which expects a card (creature) as return. The script must set that information within the environment (c.E.mTargetCardTo = target;)

Apart from being given a whole lot of information about the current game - script calls are usually stateless. Meaning usually script calls don't rememeber anything. Which also means a whole battalion of information must be collected from available resources in order to procede in any meaningfull way. The script does that by using some "helper" methods. (Things like - who is the current player, what card is played, how muchlife is left, is this a good card (buffing ourself or creatured) or harmfull card doing damage or destroying a creature etc) (by usually I mean, there is a backdoor to implemented state information using self made global variables)

Decision making within the script is quite easy since I implemented quite a few helper methods. If you look at the code there are calls like:

               isDamageCard(card)
      else if (isBufCard(card) )
      else if (isReplayCard(card) )

etc...
(After that you check if your opponent does have creatures, if yes, damage them, if no, check if it a "must" card, if yes chose a creature which survives the damage, if none, than chose the "cheapest" etc....)

Do something that is appropriate

This is a bit more complicated, since there are no "boundaries" to move in. The calling of these "script" is exactly the same as above. Only the situation keys differ. If the situation key is like "Handle Stack" or "Main Phase" than the area is wide open for all good and bad things.

Script calls are than done to (e.g.):
         ".\scripts\Allround\MainPhase.java"

Here the going gets rough... There are quite a lot of helper methods envolved scriped helper (via include) are collected in: "AIHelperScript.java". There are also quite a few hardcoded helpers which can be found in the package "csa.jportal.ai".

All of these are just "helpers" and are called from the script. You CAN program an AI by just scripting!

First step as allways (since stateless) is collect information. Which round are we in. How many creatures do we have, what life what cards on hand etc...

After collecting all needed information (this includes sorting of cards to only the ones which can be played (mana, and other restrictions). e.g.
(some code: )
      CardList possibleCreatures = AIHelper.onlyEnoughMana(creatureHand, lands);
      CardList possibleSorceries = AIHelper.onlyEnoughMana(sorceriesHand, lands);

      for (int i=0; i < possibleSorceries.size();i++)
      {
            Card card = possibleSorceries.getCard(i);
            if (sorceryHintMakesSenseNow(player, match, card) )
                  goodSorceries.addCard(card);
      }

(Hints I will explain later)

...

A "dubious" method (pseudo):
      selectGoodCard(goodList);

is called.

This method is more or less the heart of the current AI. Within that method is decided which card is played from hand (found in "AIHelperScript.java").

Within that method basically a "scoring" is done depending on the current situation. Scorings for:
-LAND
-HEALTH
-DAMAGE
-CREATURE
-CREATURE DAMAGE
-LAND DESTROY
-HAND

Is currently implemented. After the scoring is built the high scores are selected from top to bottom and if a card is found in hand which furthers the need of the score, than that card "wins" and will be played. The selection takes into account (e.g.) that if "CREATURE DAMAGE" is selected, to use more than one card to kill a creature, also taken into account (where buffs / debuffs are concerned) whether we plan to attack this round or not (after all buffung +3/+0 doesn't make much sense if we don't attack).

The scripts, just like any hardcoded class than tells the match to play the selected card just like this:
     match.playCard(player, card);
Thats it.

Other "interesting helpers"

There is a java - package called "csa.jportal.ai" within that package a subpackage "sim" exists.

Within that subpackage there are to noteworthy classes called:
   "CombatSim.java"
   "SorcerySim.java"

These classes are used to simulate battle situations in advance to the actual battle. Implemented is (more or less) a MiniMax algorithm (with some supportive Alpha Beta suggestions). (Actually very huge number of battle situations can occur (4 creatures against 4 creatures are likely to result in possible battle situations of over 100.000 possibilities). Because of that not ALLWAYS all situations are taken into account, if army sizes get to big, I use divide an conquour to reduce the actual possibilities).

Anyway these two classes are used to simulate pending battles and after "all" situations are tested the AI selects (by criteria) the best result and uses it to play cards or commense battle.

The criteria used are "Aggressivness" and "Intelligence"

Hints

The AI does not know any card by itself. (well but since the AI itself doesn't know anything thats ok). You COULD build an AI anyway you like, even with a gigantic switch statement for 12000+ cards (well that wouldn`t work with java...). The way I implemented the current AI is with the use of something I call "Hints"

(This is actually well explained in the available help - Hints").