书名《游戏人工智能编程》作者mat buckland
里面讲了一个west world项目。这是一个简单的玩具教学项目,为了让读者能够实现一个稍微具有智能的智能体。游戏的内容是
1一个矿工在挖金矿,会随机得到金矿,放入背包
2背包的容量有限,背包满则一定要去卖掉
3矿工有体力值,体力为0要休息。
ps,书中有口渴值的存在。但是口渴饥饿什么的都是一种“疲劳值”引入游戏就是为了增加限制条件和增加程序的复杂度。所以对于新手(我)来说还是尽量减少程序复杂度好。
实现方法:
作者谈到可以用有限状态机来实现这个矿工。矿工有3中状态
a在矿洞内
b在买金子的地方
c在家
三种状态会根据一定的条件互相转换。如a因为背包满而到b。a,b会因为没有疲劳值转到c。等等。。
我们可以定义三个类表示三种状态,可以把这三种类实现为单模式,有助于避免重复构建对象而带来的损失。为了实现多态,可以对三个状态定义一个共同的抽象基类叫state。这里的exec代表着每个状态应该执行的动作。比如在矿洞要挖矿,家就要恢复体力值,其参数body就是代表着矿工(因为这里只有一个矿工),body用于读取矿工的数据比如疲劳,如果需要改变矿工的状态。把每种状态的动作抽象到类里面这样最大的好处是便于每个状态下的修改和增加状态(比如我想增加一个口渴)。这种编程方式是状态驱动编程(估计是作者起的名字)。几个状态的编程方式你可能感觉到奇怪,构造函数被声明为私有的,因为这是为了实现单例而不得不采取的方式。这种实现方式在多线程下是不行的,但是本游戏只有一个线程,所以完全满足要求,关于多线程单例的实现自行百度(这个面试经常出现)。
public abstract class State { abstract public void exec(Enity body); }
file2(onminejava)
import java.util.Random; public class OnMine extends State { private static OnMine OnlyOne=null; Random gener=new Random(); private OnMine() { } public static OnMine getInstance() { if(OnlyOne==null) { OnlyOne=new OnMine(); } return OnlyOne; } @Override public void exec(Enity body) { // TODO Auto-generated method stub if(body instanceof Man) { Man toOperate=(Man) body; if(toOperate.getState()!=OnMine.getInstance()) { System.out.println("The man walk to mine"); toOperate.changeState(OnMine.getInstance()); } if(toOperate.getCurrentPower()==0) { System.out.println("man's pow is too low,he should sleep"); toOperate.changeState(Tired.getInstance()); return; } //work to get gold has 1/3 property to get the gold if(gener.nextInt(3)==2) { int getGold=gener.nextInt(toOperate.getMaxCapacity()); if(toOperate.getCurrentCapacity()+getGold>toOperate.getMaxCapacity()) { System.out.println("Gread have find gold but too much("+getGold+"), man should go to store"); toOperate.addToCurrentCapacity(toOperate.getMaxCapacity()-toOperate.getCurrentCapacity()); } else { toOperate.addToCurrentCapacity(getGold); System.out.println("Man get the gold "+getGold+"there captity is "+toOperate.getCurrentCapacity()); } } else { System.out.println("man don't have good luck,nothing get"); } toOperate.decreasePower(); System.out.println("man have power:"+toOperate.getCurrentPower()); if(toOperate.getCurrentPower()==0) { System.out.println("man's pow is too low,he should sleep"); toOperate.changeState(Tired.getInstance()); return; } if(toOperate.getCurrentCapacity()==toOperate.getMaxCapacity()) { System.out.println("man's bag if full and he should go to change it"); toOperate.changeState(BagFull.getInstance()); } } } }
file3(bagfull.java)
import java.util.Scanner; public class BagFull extends State { private static BagFull OnlyOne=null; private BagFull() { } public static BagFull getInstance() { if(OnlyOne==null) { OnlyOne=new BagFull(); } return OnlyOne; } @Override public void exec(Enity body) { // TODO Auto-generated method stub if(body instanceof Man) { Man toOperate=(Man) body; if(toOperate.getCurrentCapacity()!=toOperate.getMaxCapacity()) { System.out.println("error in bagfull beacause bag is not full"); new Scanner(System.in).nextLine();//system("pause") } System.out.println("man go to bank put the gold for money and he is very happy"); toOperate.addMoney(toOperate.getCurrentCapacity()*10); System.out.println("man have money"+toOperate.getMoney()); toOperate.setCurrentCapacity(0); toOperate.decreasePower(); if(toOperate.getState()!=OnMine.getInstance()) { System.out.println("man should move to the mine"); toOperate.changeState(OnMine.getInstance()); } } } }
file4(tired.java)
import java.util.Scanner; public class Tired extends State { private static Tired OnlyOne=null; private Tired() { } public static Tired getInstance() { if(OnlyOne==null) { OnlyOne=new Tired(); } return OnlyOne; } @Override public void exec(Enity body) { // TODO Auto-generated method stub if(body instanceof Man) { Man toOperate=(Man) body; if(toOperate.getCurrentPower()!=0) { System.out.println("there is some problem in Tired currentpow is not 0"); new Scanner(System.in).nextLine();//system("pause") } System.out.println("man is tired and should sleep a while"); toOperate.recorverPower(); if(toOperate.getCurrentCapacity()==toOperate.getMaxCapacity()) { System.out.println("man find his bag is full after sleep,he should go to store it "); toOperate.changeState(BagFull.getInstance()); } else { System.out.println("man is ready .and go to mine"); toOperate.changeState(OnMine.getInstance()); } } } }
状态定义过后就需要实现游戏的主角了。矿工类。为了便于多态我们需要建立一个抽象基类enity类(这个单词有错误)。建立这个类的目的是为了方便以后把统一处理游戏中的NPC。但是本游戏只有一个矿工,所以意义不大。man类中有一个函数叫做update。这个函数用于更新每一步的状态。update会调用矿工状态的exec函数。这样就实现了矿工的自动化。于是又一个吴桂的故事开始了,因为这个游戏根本没有结束,也十分乏味,你要做的就是不听的按回车键。
file enity.java
public abstract class Enity { }
file man.java
public class Man extends Enity { private int maxCapacity; private int currentCapacity; private int maxPower; private int currentPower; private int money; private State preState; private State state; public Man() { maxCapacity=100; currentCapacity=0; maxPower=10; currentPower=maxPower; money=0; preState=null; state=OnMine.getInstance(); } public void update() { state.exec(this); } public void changeState(State toState) { preState=state; state=toState; } public State getState() { return state; } public int getCurrentCapacity() { return currentCapacity; } public int getMaxCapacity() { return maxCapacity; } public void addToCurrentCapacity(int src) { if(src<0||currentCapacity+src>maxCapacity) { System.out.println("addtoCurrentcapacity error src negative or capacity overflow"); } this.currentCapacity+=src; } public void setCurrentCapacity(int src) { currentCapacity=src; } public void decreasePower() { this.currentPower--; if(currentPower==0) { changeState(Tired.getInstance()); } } public int getMoney() { return money; } public void addMoney(int src) { money+=src; } public void setMoney(int src) { money=src; } public int getCurrentPower() { return currentPower; } public void recorverPower() { currentPower=maxPower; } }
在编程的过程中,我发现一个问题就是这样的编程虽然便于状态的扩展,但是每个状态的逻辑非常复杂,比如在每个状态减掉体力值后,一定要首先判断体力值是不是0,然后才能继续判断其他的事情。我想到的改进方法就是把所有状态改变都放到一个控制类里面,由控制类完成状态的改变。这样可以避免多次判断体力值的问题。我不知道作者在之后会不会改进这个类。为了方便大家,我把这个游戏的代码也放到了gitoschina上。地址在这里点击打开链接