Atomix Tutorial Series: CHAPTER 1) The card game (Preview video - 70% complete)

FIRST WORDS

Dear guys,
Long time ago I planed to make tutorial series in making games in JME3 and done some examples. I waited for our project to reach a stable status. May be now is the best time to show them up!
A lot of changes have been make to the core and also a lot of excited and amazing contributions into the world of JME this day (you can name some).

OK, my project had changed too Atomix Tutorial Series (short as ATS). I decided not to do the voice since it will make you guys too difficult to flow cause I’m not a native speaker. So i will write the tutorials down step by step and also do slideshows and videos for them.

This is the first Chapter of the series and named “The card game”.

My intention is to make a “clone” Card game as Yugi Oh card game (similar to Collectable Magic Cards game). Why “clone”??? Cause I want to run fast through the concept and idea step, hoping you guys already know what the game should look like. I will also to Asset making videos but keep them short as possible. Now let’s begin!!!

CHAPTER 1: THE CARD GAME
Basic idea:
Clone Yugioh - the card game. In the game, two players use magic card with attack and defend to play with each other
Detailed game rule can be viewed here: http://yugioh.wikia.com/wiki/Master_Rules

Skills will be used, GOOD things that you should CATCH:

  • Atom framework
  • Picking
  • Scripting with Groovy
  • Basic animation, effects
  • Import 3D

Advanced potentials of the game:

  • AI, rule base
  • Web harvest for data
  • Tournament

STEP 1) Know what we going to do:

Image

Preview Video:
[video]http://youtu.be/H3JSPOqeKSE[/video]

Full complete project download: Download from SVN
Starter project (with assets) download: Download from SVN

STEP 2) Atom framework:
Short:

Detailed: Atom topic link
Click to this topic to learn how to setup Atom, AtomAI, Groovy and stuffs…

STEP 3) First run:
Copy the complete working code and run the game, enjoy it for a while!
Now go back and make another project with the base starter code.
After refactoring, you should have the game runing succesfully and just show up the main menu.

1 Like

ADVANCED TECHNIQUES
STEP 5) CODING:

Now the prepare steps are almost complete, we start coding our game!

This will be a big but interesting challange for who really want to start a serious game development career and even for who just want to learn how to do it. I can present my self almost an artist (60% vs 40% of a programmer :p).. So, i prefer doing cool stuff instead of hard stuff. That's why i played with both Java and Groovy. In this section, from time to time, you will find how Groovy save your life with clean and bright syntax instead of verbose big bad Java.

5.1) World
First consider the Table a GameLevel of a World. In that specific enviroment, cards are showed, moved rotated…etc…

5.2) Entity
Consider the a Card as a most basic Entity
[ Warning: Don’t mistake I’m going to use any kind of Composing Entity System here! ].

An Card Entity should contains:
5.2.a)
5.2.b) Make the card show
5.3) Gameplay:

5.4) Select: This section is quite advance for who don’t similar with groovy but keep your self cheerful cause you going to enjoy Groovy as much as I do!
5.4.a) Select function:
5.4.b) Select condition:
5.4.c) Cached conditions - Rules:

5.5) GUI: I presume you have basic knowledge of NiftyGUI, I will show you how to do some advance tricks of course :
5.6) Effects: :amused:

6) AI (aka Artifact Intelligent):
In this section I want to introduce the AtomAI library. It named after Atom project but in fact it just a bunch of wraper for existed contribute library in various aspect in AI area: FSM, Behavior Tree, Decision Tree, Learning machine… Futher more, I add a lot of GUI tool to deal with creating and combine sophisticated AI.

For this Magic cardgame, I only use two simplest AI technique : Behavior/Decision Tree and Minimax, as simple as shorter than 100 lines of code. But the resulted Card AI can play with human newbie opponent and even can win sometimes.

STEP 5) CODING:

Now the prepare steps are almost complete, we start coding our game!

This will be a big but interesting challange for who really want to start a serious game development career and even for who just want to learn how to do it. I can present my self almost an artist (60% vs 40% of a programmer :p).. So, i prefer doing cool stuffs instead of hard stuffs. That's why i played with both Java and Groovy. In this section, from time to time, you will find how Groovy save your life with clean and bright syntax instead of verbose big bad Java. Groovy syntax is very similar to java, in fact you can use all java syntax in groovy plus almost time, it's much more shorter than wrinting code in a Java version. You can get started with groovy in just few hours here. I also will explain a little bit of Groovy in the way.

Now LET START!
5.1) Game Stage and Game play
Here is where you clean up your game design, and write down a few most obvious Classes that build up the game.

CardGame : obvious
Card : represent a Card in the game
CardPlayer extends Player: a Player in Atom framework have basic ID, name, character…, already have built-in mechanism to go online -multiplayers if need!
CardTable extends GameLevel: represent the Table where two opponent sit and duel. Of course we can have different levels (in different Modes) like when Player traveling around and play in the future!
CardMatch : a Match can be held between two CardPlayer opponents
Turn : a Turn represent a Turn in this card game. Has index number and a point to a Player take that Turn. a Turn has 6 TurnPhase (s)
TurnPhase : DrawPhase, MainPhase, MainPhase2… contains a bunch of CardAction
CardAction : obvious. action taken of Player, like : add one more card in his hand and summon etc…

see the full implementation in the source code. Detailed explanation below!
5.1) Entity

5.2.a) Card
Consider the a Card as a most basic Entity.
[ Warning: Don’t mistake I’m going to use any kind of Composing Entity System here! ].

An Card Entity should contains infomations relate to a real card in a card game, it also has a representation, which is a 3D Model in JME scenegraph. That’s why Card class extends SpatialEntity.

SpatialEntity This is the handy parent class for classes in Atom framework which also holding Entity data, and has a Spatial as representation. Atom use a built-in two-way connection between the Entity - Spatial, a mechanism which helps you do almost anything you want with SpatialEntity (select-picking,enable/disable...ect)

Information relate to the orginal Magic card game:
[java]
String cardId=""
String name = “”
String picture = “”

int attack = -1
int defend = -1
String cardType = “”

String attribute = “”
[/java]
[…full in the source code.The meanings are obvious.]
If you declare like this in Groovy, that mean each of these fields are a Property, which have basic generated Setter/Getter by Groovy compiler.
We need one more attribute:
[java]
Card orgCard
[/java]
This hold a link to the orginal Card in the CardLibrary, which keeps information of every card avaiable in this game. We will implement this class later.

Card.groovy
[java]
package magiccard.gameplay

import com.jme3.scene.Spatial
import sg.atom.entity.SpatialEntity
/**
*

  • @author cuong.nguyenmanh2
    */
    public class Card extends SpatialEntity{
    String cardId=""
    String name = “”
    String picture = “”

    int attack = -1
    int defend = -1
    String cardType = “”

    String attribute = “”
    String cardSubType = “”
    int level = -1

    String summonRule = “”
    String effect = “”
    String desc = “”
    String longDesc = “”
    String character = “”
    String flipScript=""
    String effectScript=""
    String rarity=""
    Card orgCard

    public Card(String name){
    super(name,name)
    this.name = name
    }

    public Card(Card orgCard){
    super(orgCard.name,orgCard.name)
    this.orgCard = orgCard
    this.cardId = orgCard.cardId
    this.name = orgCard.name
    this.picture = orgCard.picture
    this.attack = orgCard.attack
    this.defend = orgCard.defend
    this.cardType = orgCard.cardType
    this.level = orgCard.level
    this.desc = orgCard.desc
    this.longDesc = orgCard.longDesc
    }
    }
    [/java]

[=============================== TO BE CONTINUE=====================================================]
Scroll to next few posts to continue, I have trouble with long posts in Wordpress!!

I think this would be good as a wiki page :slight_smile:

1 Like

YuGiOh ftw, although I haven’t played it in years :smiley: Very cool topic!

1 Like

It’s time to d-d-d-d-d-d-d-duel!

1 Like

Great work. I’d love to have this on the wiki. My only fear is that the use of trademarked materials could get us in trouble.

1 Like

@erlend_sh :
Yeah, I can also put the compelete tut into wiki and it will be much more comforable to edit and view compare to a long post. Regarding the trademarked content, I can simple use different images, but in fact, the game idea and gameplay is really the same cause all magic card game is from the same root. Anyway, the assets could be change, if you want to use it in the wiki, i wall change all the trouble some assets.

Thanks,
Atomix

1 Like

That’d work. Change all the trademarked stuff and you’d be good to go on the wiki :slight_smile:

And don’t feel nervous about those videos. What’s happening on screen is the most important part, and the scpeech can always be subtexted. Besides, it’s a great English exercise! Right @nehon ? :smiley:

1 Like
@erlend_sh said: And don't feel nervous about those videos. What's happening on screen is the most important part, and the scpeech can always be subtexted. Besides, it's a great English exercise! Right @nehon ? :D
Yeah, don't mind the mockeries. if you feel uncomfortable just picture any of us speaking Vietnamese, it'll give you confidence ;)
2 Likes
@erlend_sh said: That'd work. Change all the trademarked stuff and you'd be good to go on the wiki :)

In a quick thought may be i will do the wiki for the next tutorial, the RTPG game, because it’s all orginal from the start, all assets story and such…No more headache and I don’t have to invest too much time in rewrite this finished game.

In a few days, I will finish this tutorial, and post the next one. :stuck_out_tongue:

5.2.b) Show the card

According to the Atom framework, the representation part of a Card should be provided by a EntityFactory. In this implementation, I just ask the GameLevel - The CardTable, to made it up, cause it’s really simple.

Now consider a Card spatial built from a flatten 3d box and 2 sides: front & back with approriate textures title FrontTexture and BackTexture. In YugiGame, BackTexture are the same for all the Card, remember that we already set it in the SceneEditor! Now our job is to load the Card model and change the FrontTexture to the path in card.picture.

This is a part of the CardTable class which create and “attach” the Card spatial to the levelNode, add two more controls for movement handle and select handle. Simple isn’t it?

[java]
public void createCardOrg(){
cardOrg = ((Node) ((Node) assetManager.loadModel(“Models/Cards/Template/card1.j3o”)).getChild(“Card”)).getChild(“Cube1”);
}
Geometry createCard(Card card) {
// extract the card info
String path = card.picture

    // create the geometry
    Geometry newCard = (Geometry) cardOrg.clone();
    Material cloneMat = newCard.getMaterial().clone();
    cloneMat.setTexture("ColorMap2", assetManager.loadTexture(path));
    newCard.setMaterial(cloneMat);
    //cloneMat.setBoolean("MixGlow",true);
    levelNode.attachChild(newCard);
    card.spatial = newCard;
    newCard.setLocalScale(-0.4f,-0.4f,0.4f)
    // attach the control
    newCard.addControl(new CardSpatialControl(worldManager,card));
    newCard.addControl(new CardSelectControl(worldManager,gamePlayManager));
    return newCard;
}

[/java]

5.2.c) Card movement

For this kind of game, I just want to introduce most basic movement form, Steering movements with brace, veclocity..etc will be introduced in next tuts.
By basic movement forms, I want to talk about: - MOVETO: Move smoothly in a straight line from one point to another. - ROTTO: Rotate smoothly by Quaternion from one angle to another. - GIGGLE : Shaking, for example prepare to explode! These 3 basic movement types are already enough to compose quite fascinated effects if you know how to. See by your self in the video in the first post!

The most normal and obvious way to put movements into a Spatial in JME3 is to make an Control, name it CardSpatialControl.
[java]
package magiccard.gameplay

import com.jme3.scene.control.AbstractControl
import com.jme3.math.Vector3f
import com.jme3.math.FastMath;
import sg.atom.entity.SpatialEntityControl
import sg.atom.stage.SelectManager
import sg.atom.stage.WorldManager
import com.jme3.math.Quaternion
/**
*

  • @author hungcuong
    */
    public class CardSpatialControl extends SpatialEntityControl{

    boolean stopMove = false;
    boolean stopRot = false;
    boolean gigle = false;
    Vector3f targetPos;
    Vector3f oldPos;
    Quaternion oldRot;
    Quaternion targetRot;
    float timeRot = 0;
    float speedRot = 0.3f;
    float speedPos = 0.04f;

    public CardSpatialControl(WorldManager worldManager, Card card) {
    super(worldManager, card);
    }

    void pos(Vector3f targetPos){
    this.targetPos = targetPos.clone();;
    this.oldPos = spatial.getLocalTranslation().clone();
    stopMove = false;
    }
    void rot(Quaternion targetRot){
    this.targetRot = targetRot.clone();
    this.oldRot = spatial.getLocalRotation().clone();
    timeRot = 0;
    stopRot = false;
    }

    public void controlUpdate(float tpf){
    updatePos(tpf);
    updateRot(tpf);
    if (gigle){
    updateGiggle();
    }
    }
    void updatePos(float tpf){
    if (targetPos!=null){
    Vector3f newPos;
    Vector3f currentPos = spatial.getLocalTranslation();
    float dis = currentPos.distance(targetPos)
    if (!stopMove){
    if ( dis> speedPos){
    Vector3f force = targetPos.subtract(currentPos).normalize().mult(speedPos)
    newPos = currentPos.add(force)
    } else {
    newPos = targetPos.clone();
    stopMove = true;

             }
             spatial.setLocalTranslation(newPos)
         }
         
     }
    

    }

    void updateRot(float tpf){
    if (targetRot!=null&&oldRot!=null){
    if (!stopRot){
    Quaternion newRot = new Quaternion();
    if ( timeRot <speedRot){
    newRot.slerp(oldRot,targetRot,(float)(timeRot/speedRot))
    timeRot += tpf;
    } else {
    stopRot = true;
    newRot.set(targetRot)
    }
    spatial.setLocalRotation(newRot);
    }
    }
    }

    void updateGiggle(){
    float x =(0.5f-FastMath.nextRandomFloat()) * 0.005f;
    float y =(0.5f-FastMath.nextRandomFloat()) * 0.005f;
    float z =0;
    Vector3f oldPos=spatial.getLocalTranslation().clone();
    spatial.setLocalTranslation(oldPos.add(x,y,z));
    }
    }
    [/java]
    5.2.d) With 3D Model:
    When the card is firmly place in the ground, we want to show its attached 3D model. We can do it with a few line of codes.

[=============================== TO BE CONTINUE=====================================================]
Scroll to next few posts to continue, I have trouble with long posts in WordPress!! :smiley:


5.3) World
First consider the Table a GameLevel. In that specific enviroment, cards are showed, moved rotated…etc…

We need the world to calculate postions the card place and for the translating: the target position of which the card will head to. Remember that we made a Table texture from a 4x4 Grid.

Now use that and do simple math ;O :

[java]
package magiccard.gameplay

import com.jme3.asset.AssetManager
import com.jme3.material.Material
import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f
import com.jme3.scene.Node
import com.jme3.scene.Geometry
import com.jme3.scene.Spatial
import com.jme3.scene.shape.Box
import com.jme3.scene.shape.Quad
import sg.atom.gameplay.GameLevel
import sg.atom.stage.GamePlayManager
import sg.atom.stage.WorldManager

import com.jme3.material.RenderState
import com.jme3.renderer.queue.RenderQueue
import com.jme3.scene.shape.Sphere
import com.jme3.math.Quaternion
import com.jme3.math.FastMath
import magiccard.stage.CardSelectControl
import sg.atom.fx.particle.ParticleFactory
import sg.atom.fx.particle.ExplosionNodeControl
import com.jme3.light.PointLight
import com.jme3.effect.ParticleEmitter

class CardTable extends GameLevel{

private Spatial cardOrg;
AssetManager assetManager

// Layout to position cards and table
Vector3f center = new Vector3f(3,0,-5);
float handLength= 2.5f
float spaceBetweenCard = 0.5f
float peakPos= 0.2f
float boardSizeX = 16;
float boardSizeY= 16;

Vector3f scaledCardSize = new Vector3f(1.9f,2.7f,0.02f);
Vector3f gridGapSize = new Vector3f(1,1,1);
public static faceUpQuat = new Quaternion().fromAngles(0f,FastMath.PI,0f);
public static faceDownQuat = new Quaternion().fromAngles(-FastMath.PI,-FastMath.PI,0f);
List actions =[]

public CardTable(GamePlayManager gameplay,WorldManager world,String name,String path){
super(gameplay,world,name,path)
assetManager = world.assetManager
}

Quaternion getCardRot(boolean faceUp){
if (faceUp){
return faceUpQuat.clone()
} else {
return faceDownQuat.clone()
}
}

/*

  • Load level, override the method in GameLevel class
    */
    public void loadLevel(){
    super.loadLevel()
    createCardOrg();
    createCardBoard();
    createEffects();
    }

/*

  • Short for new Vector3f
    */
    Vector3f vec3(float x,float y,float z){
    return new Vector3f(x,y,z);
    }

/*

  • Create the Card board model
    */
    public void createCardBoard() {
    //levelNode = new Node(“LevelNode”);

Quad cardBoardShape = new Quad(boardSizeX,boardSizeY);
Geometry cardBoard = new Geometry(“CardBoardGeo”, cardBoardShape);

Material mat = new Material(assetManager, “MatDefs/ColoredTextured.j3md”);
mat.setTexture(“ColorMap”, assetManager.loadTexture(“Textures/CardBoard/DiffuseTex.png”));
mat.setColor(“Color”, new ColorRGBA(1, 1, 1, 0.9f));
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
cardBoard.setMaterial(mat);
cardBoard.setLocalTranslation(center.add((float)(-boardSizeX/2),(float)(-boardSizeY/2),0f));
cardBoard.setQueueBucket(RenderQueue.Bucket.Transparent);

levelNode.attachChild(cardBoard);
}
/*

  • For debuging positions
    /
    public void createSphere(float size,ColorRGBA color,Vector3f pos){
    Sphere sh=new Sphere(8,8,size)
    Geometry sGeo = new Geometry(“S”,sh)
    Material mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
    sGeo.material = mat
    mat.setColor(“Color”,color)
    sGeo.localTranslation = pos
    levelNode.attachChild(sGeo)
    }
    /
  • Create the orginal Card model
    */
    public void createCardOrg(){
    cardOrg = ((Node) ((Node) assetManager.loadModel(“Models/Cards/Template/card1.j3o”)).getChild(“Card”)).getChild(“Cube1”);
    }

public void createDeck(CardPlayer player) {
// create Cards for player

player.currentDeck.cardList.eachWithIndex{card,i->
//fromDeckToHand(player)
def newCard = createCard(card)
}
arrangeDeck(player)
}

public void createHand(CardPlayer player){
player.hand.eachWithIndex{card,i->
//fromDeckToHand(player)
def newCard = createCard(card)
}
arrangeHand(player)
}

public void arrangeDeck(CardPlayer player){
int numOfCards = player.currentDeck.cardList.size()

Vector3f centerDeck = getCenterHandPos(player);
Vector3f gap = vec3(0.4f,0,0.02f);
Vector3f handSize = vec3(0,0,0.5f);

gap = handSize.divide(vec3(1,1,(float) numOfCards));
player.currentDeck.cardList.eachWithIndex{card,i->
//fromDeckToHand(player)
def newCard = card.spatial

//Quaternion rotPIY =new Quaternion().fromAngleAxis(FastMath.PI,Vector3f.UNIT_Y);
//Quaternion rotPIZ =new Quaternion().fromAngleAxis(FastMath.PI,Vector3f.UNIT_Z);
//Quaternion rotXYZ =new Quaternion().fromAngles(0f,FastMath.PI,0f)
//newCard.setLocalRotation(rotXYZ)

Vector3f cardPos = gap.mult(vec3((float)i,1f,(float)i));
Vector3f deckPos;
if (isCurrentPlayer(player)){
deckPos =vec3(9f,-6.5,-5)
} else {
deckPos =vec3(-3f,6.5,-5)
}
newCard.setLocalTranslation(deckPos.add(cardPos))
}
}
public void arrangeHand(CardPlayer player){
int numOfCards = player.hand.size()

Vector3f centerHand = getCenterHandPos(player);
Vector3f gap = vec3(0.4f,0f,0.02f);
float squareSize= boardSizeX-6f
Vector3f handSize = vec3(squareSize,0f,0.02f);

gap = handSize.divide(vec3((float) numOfCards,1,1));
boolean faceUp = isCurrentPlayer(player);
player.hand.eachWithIndex{card,i->
//fromDeckToHand(player)
def newCard = card.spatial

Quaternion rotPIY =new Quaternion().fromAngleAxis(FastMath.PI,Vector3f.UNIT_Y);
Quaternion rotPIZ =new Quaternion().fromAngleAxis(FastMath.PI,Vector3f.UNIT_Z);
Quaternion rotXYZ =getCardRot(faceUp || gamePlayManager.debugMode)
//newCard.setLocalRotation(rotXYZ)

Vector3f cardPos = gap.mult(vec3((float)i,1f,(float)i));
Vector3f handPos = centerHand.add(0f,0f,0f);
//newCard.setLocalTranslation(handPos.add(cardPos));
CardSpatialControl cc=getCardControl(card.spatial)
cc.pos(handPos.add(cardPos))

cc.rot(rotXYZ)

}

}
Geometry createCard(Card card) {
// extract the card info
String path = card.picture

// create the geometry
Geometry newCard = (Geometry) cardOrg.clone();
Material cloneMat = newCard.getMaterial().clone();
cloneMat.setTexture(“ColorMap2”, assetManager.loadTexture(path));
newCard.setMaterial(cloneMat);
//cloneMat.setBoolean(“MixGlow”,true);
levelNode.attachChild(newCard);
card.spatial = newCard;
newCard.setLocalScale(-0.4f,-0.4f,0.4f)
// attach the control
newCard.addControl(new CardSpatialControl(worldManager,card));
newCard.addControl(new CardSelectControl(worldManager,gamePlayManager));
return newCard;
}

CardSpatialControl getCardControl(Spatial spatial){
return spatial.getControl(CardSpatialControl.class)
}

void putToGrave(){

}
boolean isCurrentPlayer(CardPlayer player){
return gamePlayManager.isCurrentPlayer(player)
}

Vector3f getCenterHandPos(CardPlayer player){
Vector3f centerHandPos;
float halfSizeY = (float)((boardSizeY - 0.3f)/2f);
if (isCurrentPlayer(player)){
centerHandPos = center.add(0f,-halfSizeY,0.5f)
} else {
centerHandPos = center.add(0f,halfSizeY,0.5f)
}
return centerHandPos;
}

Vector3f handPos(CardPlayer player,int index,boolean peak){
Vector3f centerHandPos = getCenterHandPos(player)

int numHandCards =player.hand.size()
float indexf=0

if (index == -1){
numHandCards ++
indexf = numHandCards
} else if (index == -2){
indexf = numHandCards + 2
} else if (index == -3){
indexf = numHandCards
numHandCards = 5
}
horizontalPos = handLength * indexf + numHandCards

Vector3f nextCardPos = vec3(0,horizontalPos,0).add(centerHandPos)

if (peak){
nextCardPos.add(vec3(0,peakPos,0))
}
return nextCardPos;
}

Vector3f deckPos(CardPlayer player){
if (isCurrentPlayer(player)){
return vec3(1,1,1)
} else {
return vec3(1,4,1)
}
}
Vector3f gravePos(CardPlayer player){
if (isCurrentPlayer(player)){
return vec3(1,1,1)
} else {
return vec3(1,4,1)
}
}
Vector3f groundPos(CardPlayer player,int index){
if (isCurrentPlayer(player)){
return vec3(-0.8f,-1.7f,-5).add(scaledCardSize.mult(vec3((float)index,0f,0f)))
} else {
return vec3(-0.8f,1.7f,-5).add(scaledCardSize.mult(vec3((float)index,0f,0f)))
}
}
Vector3f magicPos(CardPlayer player,int index){
if (isCurrentPlayer(player)){
return vec3(-0.8f,-4.5f,-5).add(scaledCardSize.mult(vec3((float)index,0f,0f)))
} else {
return vec3(-0.8f,4.5f,-5).add(scaledCardSize.mult(vec3((float)index,0f,0f)))
}
}
void fromDeckToHand(CardPlayer player,Card card){
Vector3f nextCardPos=handPos(player,-3,false)
CardSpatialControl cc=getCardControl(card.spatial)
cc.pos(nextCardPos)
//cc.faceUp()
//cc.moveTo(nextCardPos,3)
}
void fromHandToMagic(CardPlayer player,Card card){
Vector3f nextCardPos=magicPos(player,player.magic.size())
CardSpatialControl cc=getCardControl(card.spatial)
cc.pos(nextCardPos)
cc.rot(getCardRot(false))

}

void fromHandToGround(CardPlayer player,Card card){
Vector3f nextCardPos=groundPos(player,player.ground.size())
CardSpatialControl cc=getCardControl(card.spatial)
cc.pos(nextCardPos)

}
public Vector3f getCamPos(){
return center.add(0f,-8f,20f)
}
void addExplosion(Vector3f pos){
def explosion = explosionPrefab.clone();
explosion.localTranslation =pos.clone();
/*
levelNode.attachChild(flame);
levelNode.attachChild(rain);
levelNode.attachChild(spark);
*/
levelNode.attachChild(explosion);
}
void destroy(CardPlayer player,Card card){
addExplosion(card.spatial.localTranslation)
CardSpatialControl cc=getCardControl(card.spatial)
cc.gigle = true;
actions<<[
time:1f,
do:{
card.spatial.removeFromParent()
}
]
}

public void simpleUpdate(float tpf){
// see if anything queued
for (Iterator it=actions.iterator(); it.hasNext(); ) {
def action = it.next();
if ((action.time -= tpf) <0) {
action.do()
it.remove();
} else {
// do nothing but wait
}
}
}

Node explosionPrefab;
void createEffects(){
ParticleFactory pf = new ParticleFactory(assetManager);
/*
ParticleEmitter flame = pf.createFlame();
flame.setLocalTranslation(new Vector3f(6f,4f,-5f));

ParticleEmitter rain = pf.createRain("");
rain.setLocalTranslation(new Vector3f(0,0,-5f));

ParticleEmitter spark = pf.createSpark();
spark.setLocalTranslation(new Vector3f(0,0,-5f));
*/
explosionPrefab = pf.createExplosionNode();
explosionPrefab.getControl(ExplosionNodeControl.class).setMaxTime(2f)
}
public void loadCharacters(){

Quaternion rot= new Quaternion().fromAngleAxis(FastMath.HALF_PI,Vector3f.UNIT_X)
/*
Spatial demon = assetManager.loadModel(“Models/Demons/BlueEyeWhiteDragon/BlueEyes.j3o”);
demon.scale(0.04f);
demon.setLocalRotation(rot);
levelNode.attachChild(demon);
*/
Spatial witch = assetManager.loadModel(“Models/Demons/Magicians/White/WhiteFemaleMagician.j3o”);
witch.scale(0.5f);
witch.setLocalTranslation(new Vector3f(3f,4f,-5f));
witch.setLocalRotation(rot);
levelNode.attachChild(witch);

Spatial samurai = assetManager.loadModel(“Models/Demons/Warior/SamuraiWarior.j3o”);
samurai.setLocalTranslation(new Vector3f(6f,4f,-5f));
samurai.scale(0.5f);
samurai.setLocalRotation(rot);
levelNode.attachChild(samurai);

/** A white, spot light source. */
PointLight lamp = new PointLight();
lamp.setPosition(getCamPos());
lamp.setColor(ColorRGBA.White);
levelNode.addLight(lamp);
}
}
[/java]

Explanation:

The CardTable take care of 3 things: (1| Creating:
  • - Its self from a 16x16 Quad and the Texture
  • - The Cards from the OrgCard model with appropriate picture
  • - The effects
  • - The 3D models
(2| Destroying things in delayed fashion:
  • - Explode the Card (cool effect!)
(3| Calculate the postions,directions of all action triggered by GamePlay. Arrange and reArrange the cards in hands, in Deck. handPos,gravePos,groundPos, deckPos...etc
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 5.4) Select: This section is quite advance for who don't similar with groovy but keep your self cheerful cause you going to enjoy Groovy as much as I do!

In Atom, I introduce a generic way to hook your select function into your code game mechanic.
Short:

Detailed:

For this card game, we will implement a few things in this designed mechanic:
5.4.a) Select function:
[java]
// Add later
[/java]
5.4.b) Select condition:
[java]
// Add later
[/java]
5.4.c) Select control:
CardSelectControl.java
[java]
package magiccard.stage;

import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import sg.atom.stage.WorldManager;
import sg.atom.stage.select.SpatialSelectControl;
import magiccard.gameplay.CardGamePlay;
import magiccard.gameplay.Card;
import magiccard.gameplay.CardSpatialControl;
import sg.atom.stage.select.EntitySelectCondition;

/**
*

  • @author cuong.nguyenmanh2
    */
    public class CardSelectControl extends SpatialSelectControl {

private Vector3f bigScale;
private Vector3f orginalScale;
private Vector3f orginalPos;
private Vector3f peakPos;
private boolean skipOutHover;
private String currentFunction;
private CardGamePlay gameplay;
private Card card;
private boolean isBigger = false;
private boolean isPeak = false;
private boolean isHighlight = false;

public CardSelectControl(WorldManager worldManager, CardGamePlay gameplay) {
currentFunction = “Normal”;
this.gameplay = gameplay;
}

@Override
public void setSpatial(Spatial spatial) {
super.setSpatial(spatial);
this.card = (Card) spatial.getControl(CardSpatialControl.class).getEntity();
orginalScale = spatial.getLocalScale().clone();
bigScale = orginalScale.mult(1.2f);
orginalPos = spatial.getLocalTranslation().clone();
}

@Override
protected void doSelected() {
super.doSelected();
//orginalScale = spatial.getLocalScale();
//orginalPos = spatial.getLocalTranslation().clone();
//peakPos = orginalPos.add(new Vector3f(0f, 0.4f, 0f));
//spatial.setLocalTranslation(peakPos);
//spatial.setLocalScale(bigScale);
}

@Override
protected void doDeselected() {
super.doDeselected();
//spatial.setLocalScale(orginalScale);
//spatial.setLocalTranslation(orginalPos.clone());

}

@Override
protected void doHovered() {
if (this.isHoverable()) {
super.doHovered();
//orginalScale = spatial.getLocalScale();
EntitySelectCondition inHand = (EntitySelectCondition) gameplay.cardSelectRules.get(“inHand”);
EntitySelectCondition inGround = (EntitySelectCondition) gameplay.cardSelectRules.get(“inGround”);
if (inHand.isSelected(card)) {
peak();
bigger();
highlight();
} else if (inGround.isSelected(card)) {
// The card is the ground
highlight();
}
}
}

@Override
protected void doOutHovered() {
if (this.isHoverable()) {
if (!this.skipOutHover) {
super.doOutHovered();

noPeak();
smaller();
noHighlight();

}
}
}

public String getCurrentFunction() {
return currentFunction;
}

public void setCurrentFunction(String currentFunction) {
this.currentFunction = currentFunction;
}

public void setSkipOutHover(boolean skipOutHover) {
this.skipOutHover = skipOutHover;
}

public boolean isSkipOutHover() {
return skipOutHover;
}

void bigger() {
if (!isBigger) {
spatial.setLocalScale(bigScale);
isBigger = true;
}
}

void peak() {
if (!isPeak) {
orginalPos = spatial.getLocalTranslation().clone();
peakPos = orginalPos.add(new Vector3f(0f, 0.2f, 0.2f));
spatial.setLocalTranslation(peakPos);
isPeak = true;
}
}

void noPeak() {
if (isPeak) {
spatial.setLocalTranslation(orginalPos.clone());
isPeak = false;
}
}

void smaller() {
if (isBigger) {
spatial.setLocalScale(orginalScale);
isBigger = false;
}
}

void highlight() {
if (!isHighlight) {
((Geometry) spatial).getMaterial().setBoolean(“MixGlow”, true);
isHighlight = true;
}
}

void noHighlight() {
if (isHighlight) {
((Geometry) spatial).getMaterial().setBoolean(“MixGlow”, false);
isHighlight = false;
}
}
}

[/java]

5.4.d) Cached conditions - Rules:
We use a Groovy Map cardSelectRules to save all rule in form of Closure that determine if a Card is selected or not.

part of CardGamePlay.groovy
[java]
public Map cardSelectRules=[:];
void createSelectConditions(){
// RULE : The card is in hand
def inHandRule ={card->

CardPlayer player = whichPlayerCard(card)
if (player.getHand().contains(card)) {
return true;
}
return false;
}
cardSelectRules[“inHand”] =new ClosureSelectCondition(inHandRule);

// RULE : The card is in hand of the current player - who play this device!
def inHandCurrentRule ={card->
if (isCurrentPlayer(whichPlayerCard(card))) {
CardPlayer player = getCurrentPlayer();
if (player.getHand().contains(card)) {
return true;
}
}
return false;
}
cardSelectRules[“inHandCurrent”] =new ClosureSelectCondition(inHandCurrentRule);

// RULE : The card is in hand of the current turn player - who has this turn!
def inHandTurnRule ={card->
if (isCurrentPlayer(whichPlayerCard(card))) {
CardPlayer player = getCurrentPlayer();
if (player.getHand().contains(card)) {
return true;
}
}
return false;
}
cardSelectRules[“inHandTurn”] =new ClosureSelectCondition(inHandTurnRule);
// RULE: The card is in ground
def inGroundRule ={card->

CardPlayer player = whichPlayerCard(card);
if (player.ground.contains(card)) {
return true;
}
return false;
}
cardSelectRules[“inGround”] =new ClosureSelectCondition(inGroundRule);
// RULE: Card in player Main phase
def mainPhaseCards = {card->
if (isCurrentPlayer(whichPlayerCard(card))) {
CardPlayer player = getCurrentPlayer();
if (player.hand.contains(card)||player.ground.contains(card)||player.magic.contains(card)) {
//println(“Condition true”);
return true;
}
}
return false;
}
cardSelectRules[“mainPhaseCards”] =new ClosureSelectCondition(mainPhaseCards);
// RULE: Can not select any thing
def noneSelect = {card->
return false;
}
cardSelectRules[“noneSelect”] =new ClosureSelectCondition(noneSelect);

}
[/java]

[=============================== TO BE CONTINUE=====================================================]
Scroll to next few posts to continue, I have trouble with long posts in WordPress!! :smiley:

5.6) AI (aka Artifact Intelligent):
In this section I want to introduce the AtomAI library. It named after Atom project but in fact it’s just a bunch of wrapper for existed contributed and open-source libraries in various aspects of AI area: FSM, Behavior Tree, Decision Tree, Learning machine…

Futher more, I added a lot of GUI tools to deal with creating and combining sophisticated AI.

For this Magic cardgame, I only use two simplest AI technique : Behavior/Decision Tree and Minimax, as simple as shorter than 500 lines of code. But the resulted Card AI can play with human newbie opponent and even can win sometimes. Our implementation in java or groovy already have Data Model supported by AtomAI library.

Now back to the basic, in a general card game, gameplay consist of fews ideas:

  • Which card to choose
  • Which state of that card to choose
    In a magic card game, two opponents including AI should concern a few more and how to answer these question to defeat the other:
  • Which magic card support/destroy which card
  • How to get Best health point and best damage

A - Tattic and stragegies
Now I kind of don’t want to write much about AI in the first tutorial but let’s go, it wont take too long.

In this game, the AI should have appropriate stragegies from Phase to Phase, even though, we should have a global stragegy for each round. Here are “naive” stragegies of the magic card AI:

Summon Cards (Main phase 1&2):

In the MainPhase, if you can summon, that mean there is card for you to have a valid summon. Wisely choose one option:
|- STRAGEGY 1) Choose the biggest attack/defend card.
When you don’t have enough appropriate tribute cards to summon a “many stars” Card

  • Just summon the biggest attack\defend card you have.
  • To decide to summon (attack||defend), check what cards are in the field at the moment and compare your cards to your opponent cards, if the result is :
    |- “GoodToAttack” - choose Biggest Attack - Attack State
    |- “ShouldDefend” - choose Biggest Defend - Defend state
  • summon it, and to the next Phase

|- STRAGEGY 2) Choose fews smallest card to tribute and summon a biggest one.
When you HAVE enough appropriate tribute cards to summon a “many stars” Card (always with big attack/defend points and have effects)

  • Choose the “biggest” and “most many stars” card.
  • Choose the “smallest” cards enough to tribute to summon this card.
    |- If you can not even summon anything, have to Pass (by the rule).
  • Futher, if there is no card in your ground mark the Brain with “Dangerous situation” and Pass.

Use support Cards (every action phases):

In every action phase, except for DrawPhase and Endphase, you can use support card like MagicCard, TrapCard…etc. We can design a function to evaluate thoose card by how good is going to be when called in the game, but for the time being, we use such simplest tragagy:

STRAGEGY 3) Use all the support cards in one turn
That mean you are very hungry for the final win. It’s not always intelligent to do so in the POV of a professional duelist but hey… Ok. Now we want to pull all out support cards in one turn.

STRAGEGY 4) Category cards in the game
You should devide the support card and its effect to type and handle it by type, that’s the naive way but appropriate for DecisionTree technique we used here.

Q&A

  • Q: How many type of support card are there?
  • A: Trap card can flip whenever opponent take action like summon or attack. Magic card can do almost every effect from increase healpoint
  • Q: How many type this naive AI know how to use?
  • A: Many type of support card and effect are there in the really game but the AI use a “DecisionTree” technique just use a few which you know exactly what it do. For more complicated intelligent, we have to use complicated AI model like Neutral network or Genetic Algorimth…:
    o) Support Point Card: Card that increase/decrease the demon cards point.
    o) Heal Card: Card that increase/decrease the opponents point directly.
    o) Special Summon Card: Card that can bring demon card to the field in special way.
    o) Destroy/Clean Card: Card that destroy demons or other support card
    o) State Constraint Card: Card that lock or unlock the Limit of the game, like noActtack, noDefend…

STRAGEGY 5) Bigger biggest - Smallest smaller

  • Q: Which Support Point Card support to which demon?
  • A: You want to combine a biggest possible Card. So all biggest support card to the biggest demon. In oposite, you want to decrease all the demon of opponent on the field to the point that you can destroy it by attack or can not attack you anymore. So you just try to find the way to decrease those attack point to
    o) the smallest defend point of your defend demon if you have any defend demon
    o) or smallest attack point if you only have attack demon
    o) or 0 if you have no demon
    We can have sotisphicated way to distribute thoose support point but just do it as simple as possible.

Decide what to do:

STRAGEGY 6) Attack who and how? Decide to attack or defend
Image you have a few demons in the field to attack your opponent.
Step1, first glance calculation:
There is 3 situation here:

  • All your demon are better than your opponent’s current avaiable cards, so you will not lose any health point in the next turn, even if your opponent attack back. It’s called Dominance in game theory - In our game, this situation call “GoodToAttack”
  • You have a few better and a few worse, you want to attack. - This situation call “Average” and need futher calulation.
  • You can not attack at all, or lost points. So you should switch to defend. - This situation call “ShouldDefend”.
    (Not to mention: Affaid of trap card will flip when you take action?)
  • In fact, 3 above situations can be calculated by dertemine “forecast” result health point of two opponents.
    Step2, further calculation:

Apply the attack of all your the demons to each of your opponent demons and calculate the result points of both, call it a case. Best case is you win directly, less good case is you cause damage a lot, worse case, you should not attack cause you lose or lost points, it’s a bad idea.
Step3, Apply:

After finish calculation, you apply the whole case in the current turn.

STRAGEGY 7) Dangerous - Prevent attack | GoodToAttack - Prevent deffend
The state constraints are very powerful stragegy when you can use it.
If you in Dangerous situation, which your AI brain mark. You can prevent your opponent not to attack in a few rounds or forever. In opposite, you can prevent your opponent to take defend by the opposite constraint.
If you only have the magic card to constraint one card, use it with the biggest or smallest card possible so your benifit is maximum!

The usage of Heal Card ,Destroy/Clean Card,Special Summon are trivial and pretty obvious by combining the previous stragegy so I ommited this part! :stuck_out_tongue:

==================================TO BE CONTINUE =====================================

B - Minimax
In the previous section, we talked about “Forecast what will happen in a few next rounds”, if you know the result, you can definitely choose a most appropriate path. There is a related problem in the field of AI Tatic, called Minimax. The basic idea of minimax is .

From wikipedia

C - Introducing FuzzyLogic from AtomAI
D - Imlementation
In 5.5) section, we already declare a few rules which can have us to filter out which card are in hand, which card are in grave, ground, magic… etc. Later we will use them as ultilites in the AI.

The first try to implement the AI/
[java]
package magiccard.gameplay.ai

import magiccard.*
import magiccard.gameplay.*
import static magiccard.gameplay.TurnPhase.TurnPhaseType.*
/**

  • This class is the AI for playing YugiOh Magic Card game.

  • The main technique used is DecisionTree & NeutralNetwork to decide actions
    */
    public class CardPlayerAI {
    enum AILevel {Starter,Normal,HardCore,Duelist,Best;
    int deep;
    }
    AILevel level;
    CardGamePlay gamePlay;
    CardPlayer player;
    enum StragegySituation {GoodToAttack,ShouldDefend,Unknown,Dangeous}
    StragegySituation situation= StragegySituation.Unknown;
    def memories = [:]
    // The list of special card (id) that will have higher priority at any time
    def listOfSpecialCards = []

    /**
    *AI sepecific params
    **/
    int maxTime = 5000
    int maxSteps = 500
    int maxBranch = 50
    int maxGuess = 5

    int randomness = 30

    // save delayed action
    def actions
    public CardPlayerAI(CardGamePlay gamePlay){
    this.gamePlay = gamePlay;
    this.actions = []
    }

    public String toString(){
    return "AI level : “+this.level.toString() + " status :” + this.situation.toString()
    }
    public int think(){
    int startTime = System.currentTimeMillis()
    if (gamePlay.currentTurn.currentPhase.type == MainPhase){
    // try to summon
    if (!gamePlay.currentTurn.currentPhase.monsterSummoned){
    def summonableCards = player.hand.findAll{card-> canSummon(card)==true};
    if (!summonableCards.isEmpty()){
    def bestCard = summonableCards.max{card-> card.attack}
    actions<
    delayedAct()
    }

     actions.clear();
    

    }
    //self action

    void summon(Card card){
    gamePlay.notifyMoveCard(card,“enableHover”)
    gamePlay.fromHandToGround(card);
    gamePlay.currentTurn.currentPhase.monsterSummoned = true;
    }
    /**

    • MINIMAX SUPPORT
    • See the game as the minimum forecastable loss problem and calculate the case
    • In fact, you can use minimax as a base heristic case and then extend
      */

    /**

    • CASE BASE SUPPORT
    • Not for a naive approach anymore.
    • In fact, you can build a heristic case and calculate the result in a few next round to see if the value you gain (win) or lost is sastified
      */
      def tryCase(Case aCase){
      return result;
      }
      def askForSelect(type,inCardList,info,condition){

    }
    public Case findBestSummonCase(){

    }
    public Case findBestMagicCase(){

    }
    public Case findBestSupportCase(){

    }
    public Case findBestOveralCase(){}

    // self evaluation
    public float evalGoodToAttack(Card card){

    }
    public float evalGoodToDefend(Card card){

    }
    /* This function evaluate the value of these two cards when the AI choose to support targetCard with the approriate supportCard*/
    public float evalGoodToSupport(Card targetCard,Card supportCard){

    }
    /* This function evaluate the value of this card when they want to keep or discard it by purpose */
    public float evalGoodOveral(Card card){

    }
    // Rule
    public boolean canSummon(Card aCard){

     // enough tribute
     int stars = aCard.level.toInteger()
     boolean enoughStar=false;
     if ( stars &gt; 6){
         if (stars - 5 &gt; player.ground.size()){
             enoughStar =true
         } else {
             enoughStar =false
         }
          
     } else {
         enoughStar=true;
     }
     //(!aCard.summonCancel) &amp;&amp; &amp;&amp; sastifySummonCondition(aCard)
     return (enoughStar &amp;&amp; aCard.isMonsterCard());
    

    }

}
[/java]

And the way to call it
… CardGamePlay
[java]
case MainPhase:
if (!currentTurn.player.aiPlayer){
if (isPlayerChangePhase(MainPhase)){
nextPhase()
} else {
humanSelectFunction();
}
} else {
println(currentTurn.player.ai);
currentTurn.player.ai.think();
currentTurn.player.ai.act();
}
[/java]

The fun things here are we delayed Action we want the AI and save to Actions list made from Closure. In the main loop, we wait till ai.act is called then execute all the delayed action. The power of Groovy once again shown! By this technique we can easily introduce paralel computing with an cool ulitities of Groovy named GPars. But we don’t use it right now though ! :roll:

ADVANCED TECHNIQUES

It seems like this example project just lead you through the most basic things you can do with JME3, but… It’s not totally true. I also want to show some advanced-cool techniques in which developers and artists can find helpful in their other projects. Those are:
Web harvest
Groovy data/config scripting

Web harvest
It comes the time in the project when you want to find a lot of references. Or, even want to find information which you know already been somewhere on the internet. Yeah! Me too. At that time, I meet Web harvest, one of the most useful tool that I use from time to time and also try my best to master it!!

If you got time just read a little bit about it to get the basic idea of what it is and what it for. In this project I want to make data by combining things from the internet for the Card game… Dont call me pirate even if I’m truly one. I use some source in the internet to harvest the information in the Yugioh Card and Packs, like description, attack, defend point…etc. and save it in to… Groovy script.
Groovy data scripting
It comes the time when you don’t want to use XML and all other things to make config and text data file, cause you just love the short and briliant syntax of Groovy. Me too… again! In Atom framework, I used Groovy to make game configuration, dialogue, cinematic scripting, facial scripting, AI scripting and even a whole GAME! That’s why I also use Groovy to save the infomations of the cards. Lucky me, Web harvest also can play with groovy like a charm, so benefit.

Detailed technique should be shown in an “in-deep” topic, here I just show a few pictures what I’ve done with this two techs for this card game: :woot: :woot:

SUMMARY

After a few thousands LoC game, I hope you guys find something useful to start working with our powerful engine JME3. In this first tutorial, I don’t want to go too much and too soon into technical problems. That’s not the way to go! :stuck_out_tongue_winking_eye:

In the next tutorial of the series, you will go straight in a bigger challenge with I think will give you more inspirations: "A Medieval RPG game" with story telling and cinematic. That's it, a real game template which you can expand to make a full blown game.

But that’s enough for this week. I planed to stop a little bit to listen to feedbacks from you guys in how i can improve the tutorials or what I should go more into detail.

Big Thanks,

Atomix
8)

@erlend_sh said: Great work. I'd love to have this on the wiki. My only fear is that the use of trademarked materials could get us in trouble.

Start a whole section for user-submitted tutorials? :). I’m sure word-press should have plugins available for something like this?

Sweet atomix! After my exams i’ll 100% run through this and give you feedback on how I found it, questions etc :). Seems like so many great things in here to learn! Thankyou!

Also could I suggest: if it makes tutorials easier to have pure video (easier for you to create/easier for other users to follow). Perhaps add subtitles to speech?

@atomix said:
SUMMARY

After a few thousands LoC game, I hope you guys find something useful to start working with our powerful engine JME3. In this first tutorial, I don’t want to go too much and too soon into technical problems. That’s not the way to go! :stuck_out_tongue_winking_eye:

But that’s enough for this week. I planed to stop a little bit to listen to feedbacks from you guys in how i can improve the tutorials or what I should go more into detail.

Big Thanks,

Atomix
8)

Links to SVN not working?

P.s. Finish exams May 16th and then i can go through and give you feedback :).

@avpeacock
Thank for your interest in the tutorials,

I didn’t not complete and submit the whole project yet, as you can see in the title, it’s just 70% of the way. Sad that I don’t have enough time to complete it right now but in a fews next day. Anyway, I will update the SVN link as soon as possible. If you want to ask anything you can PM me in the forum. Happy coding,

Best regards,