人工智能课程实验一的任务,写出猴子摘香蕉问题的知识表示,并用代码实现推理过程
问题描述
一个房间里,天花板上挂有一串香蕉,有一只猴子可在房间里任意活动(到处走动,推移箱子,攀登箱子等)。设房间里还有一只可被猴子移动的箱子,且猴子登上箱子时才能摘到香蕉,问猴子在某一状态下(设猴子位置为A,香蕉位置在B,箱子位置为C),如何行动可摘取到香蕉。
知识表示
知识表示的方法是有很多,如一阶谓词、产生式、语义网、框架等等。这里选择的是产生式表示的方法
知识的产生式表示
综合数据库
(ON, HOLD, Monkey, Banana, Box)
ON=0:猴子在地板上
ON=1:猴子在箱子上
HOLD=0:猴子没有抓到香蕉
HOLD=1:猴子抓到了香蕉
Monkey:猴子的位置
Banaba:香蕉的位置
Box: 箱子的位置
规则库
- IF 猴子位置= =箱子位置= =香蕉位置
AND 猴子ON箱子
THEN 猴子HOLD香蕉 - IF 猴子位置= =箱子位置= =香蕉位置
AND 非(猴子ON 箱子)
THEN 猴子ON箱子 - IF 猴子位置= =箱子位置!=香蕉位置
AND 猴子ON 箱子
THEN 非(猴子ON箱子) - IF 猴子位置= =箱子位置!=香蕉位置
AND 非(猴子ON 箱子)
THEN猴子位置= =箱子位置= =香蕉位置 - IF 猴子位置!=箱子位置= =香蕉位置
AND非(猴子ON箱子)
THEN 猴子位置= =箱子位置 - IF 猴子位置!=箱子位置!=香蕉位置
AND非(猴子ON箱子)
THEN 猴子位置==箱子位置
结束状态(1,1,B,B,B)
算法实现
其实用python实现最便捷,但我没有安装python环境(没写过python我是菜鸡)所以就用了Java实现,但思路不够清晰,写的很啰嗦呜呜呜
顺便做出了图形化界面,用的是学CS61b时候白嫖的StdDraw标准绘图库,把因为jdk版本问题,没有用StdDraw.java文件 ,直接把StdDraw.class文件放在lib问价夹里并在IDE里添加依赖路径就可以用了,而且IntelliJ IDEA会直接把.class文件decompile成.java文件显示,哦吼~
class设计
根据PEAS设计class
猴子是Agent,所以将Monkey作为主类。
显然:猴子可以移动,箱子可以被猴子推走,香蕉可以被摘下来,他们都可以移动,都需要有横纵坐标 以显示在图形界面不同的位置上。设计一个抽象类movable,包含他们共同的属性(横纵坐标,图片文件路径等)以及方法(显示图片)
因为只需要考虑三个位置ABC,就把问题化简了。
ps:StdDraw设定的画布原点在正中间
public abstract class movable {
public int X;
public int Y;
public String imgFileName;
public static int A = -40;
public static int B = 2;
public static int C = 40;
public static final int ceiling = 50;
public static final int floor = -30;
public static final int onBox = 10;
public static final int inHand = 20;
public void draw(){
StdDraw.picture(X,Y,imgFileName);
}
}
箱子和香蕉直接extent即可
public class Banana extends movable{
public Banana(int B){
this.X = B;
if(HOLD.isHOLD())
this.Y = inHand;
this.Y = ceiling;
imgFileName = "data/banana.png";
}
}
///
public class Box extends movable{
public Box(int C){
this.X = C;
this.Y = floor;
imgFileName = "data/box.png";
}
}
对于ON HOLD两种状态,定义成了两个静态类,方便判断
public class ON {
private static final int yes = 1;
private static final int no = 0;
private static int status;
public static boolean isON(){
return status == yes;
}
public static void setYes(){
status = yes;
}
public static void setNo(){
status = no;
}
public static void setStatus(int arg){
status = arg;
}
public static int getStatus(){
return status;
}
}
public class HOLD {
private static final int yes = 1;
private static final int no = 0;
private static int status;
public static boolean isHOLD(){
return status == yes;
}
public static void setYes(){
status = yes;
}
public static void setNo(){
status = no;
}
public static void setStatus(int arg){
status = arg;
}
public static int getStatus(){
return status;
}
}
最后就是主类monkey
因为猴子的动作(推箱子,走向箱子)等等都需要知道或改变箱子、香蕉的位置,从逻辑性和复用性的角度考虑,肯定是将实例化后的箱子,香蕉作为参数传入给猴子拥有的方法比较合理。但我只是为了完成这个实验所以干脆把Box、Banana 作为猴子的成员变量
StdDraw显示动画的原理:t时刻在(x,y)处显示图片1,清空画布,t+dt 时刻在(x+dx,y+dy)处显示图片1,达到视觉上的移动效果。所以为了在空白处显示每一步对应的产生式变化,我用allsteps存储每一步状态,每次更新的时候清空画布并且显示目前为止经历的状态,有些凌乱,都不是重点
import java.util.ArrayList;
import java.util.List;
public class Monkey extends movable{
Box box;
Banana banana;
List<List<Character>> allSteps = new ArrayList<>();
public Monkey(int A){
this.X = A;
if(ON.isON())
this.Y = onBox;
else this.Y = floor;
imgFileName = "data/monkey.png";
}
public void firstStep(){
List<Character> init = new ArrayList<>();
init.add(run.status.get(ON.getStatus()));
init.add(run.status.get(HOLD.getStatus()));
init.add(run.status.get(this.X));
init.add(run.status.get(this.banana.X));
init.add(run.status.get(this.box.X));
allSteps.add(init);
showStep();
}
public void moveToBox(){
this.X = box.X;
List<Character> sb = new ArrayList<>();
sb.add(run.status.get(ON.getStatus()));
sb.add(run.status.get(HOLD.getStatus()));
sb.add(run.status.get(this.X));
sb.add(run.status.get(this.banana.X));
sb.add(run.status.get(this.box.X));
allSteps.add(sb);
showStep();
}
public void pushToBanana(){
this.X = banana.X;
box.X = banana.X;
List<Character> sb = new ArrayList<>();
sb.add(run.status.get(ON.getStatus()));
sb.add(run.status.get(HOLD.getStatus()));
sb.add(run.status.get(this.X));
sb.add(run.status.get(this.banana.X));
sb.add(run.status.get(this.box.X));
allSteps.add(sb);
showStep();
}
public void climbOn(){
ON.setYes();
this.Y = onBox;
List<Character> sb = new ArrayList<>();
sb.add(run.status.get(ON.getStatus()));
sb.add(run.status.get(HOLD.getStatus()));
sb.add(run.status.get(this.X));
sb.add(run.status.get(this.banana.X));
sb.add(run.status.get(this.box.X));
allSteps.add(sb);
showStep();
}
public void climbDown(){
ON.setNo();
this.Y = floor;
StdDraw.clear();
List<Character> sb = new ArrayList<>();
sb.add(run.status.get(ON.getStatus()));
sb.add(run.status.get(HOLD.getStatus()));
sb.add(run.status.get(this.X));
sb.add(run.status.get(this.banana.X));
sb.add(run.status.get(this.box.X));
allSteps.add(sb);
showStep();
}
public void grasp(){
HOLD.setYes();
this.banana.Y = inHand;
List<Character> sb = new ArrayList<>();
sb.add(run.status.get(ON.getStatus()));
sb.add(run.status.get(HOLD.getStatus()));
sb.add(run.status.get(this.X));
sb.add(run.status.get(this.banana.X));
sb.add(run.status.get(this.box.X));
allSteps.add(sb);
showStep();
}
private void showStep(){
int Line = 70;
int addLine = -5;
StdDraw.clear();
this.box.draw();
this.draw();
this.banana.draw();
for(List<Character> list:allSteps){
StdDraw.text(-60,Line, list.toString());
Line += addLine;
}
StdDraw.text(movable.A,-65,"A");
StdDraw.text(movable.B,-65,"B");
StdDraw.text(movable.C,-65,"C");
StdDraw.show();
StdDraw.pause(1000);
}
}
最后就是run这个运行类,main函数在这里(包含规则库),从txt文件读取五元组的初始状态,需要从字符到坐标再到字符进行一些转化
规则库才是这个实验的重点,所以写了注释,虽然很简单
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class run {
public static final Map<Integer,Character> status = new HashMap<>();
private static void defineStatus(){
status.put(-40,'A');
status.put(2,'B');
status.put(40,'C');
status.put(0,'0');
status.put(1,'1');
}
private static int charToPosition(char c){
switch (c){
case 'A':return movable.A;
case 'B':return movable.B;
case 'C':return movable.C;
default:return 0;
}
}
public static Monkey readArgs(String path){
//从文件中读取初始状态
//猴子,香蕉,箱子的位置以及是否猴子是否在箱子上,是否拿着香蕉
In in = new In(path);
ON.setStatus(in.readInt());
HOLD.setStatus(in.readInt());
in.readChar();
Monkey monkey = new Monkey(charToPosition(in.readChar()));
in.readChar();
monkey.banana = new Banana(charToPosition(in.readChar()));
in.readChar();
monkey.box = new Box(charToPosition(in.readChar()));
//对于一些不合理的输入报警
assert !ON.isON() || monkey.X == monkey.box.X
:"illegal position when on box";
assert !HOLD.isHOLD() ||
(monkey.X == monkey.box.X) && (monkey.X == monkey.banana.X)
:"illegal position when hold banana";
assert !HOLD.isHOLD() || ON.isON():"hold banana but not on box";
return monkey;
}
public static void main(String[] args) {
defineStatus();
//读取txt文件
String fileName = args[0];
Monkey monkey = readArgs(fileName);
//画面双缓冲防抖
StdDraw.enableDoubleBuffering();
//画布大小
StdDraw.setScale(-80,80);
StdDraw.clear();
//展示猴子香蕉箱子初始状态
monkey.box.draw();
monkey.draw();
monkey.banana.draw();
monkey.firstStep();
StdDraw.show();
StdDraw.pause(1000);
if(HOLD.isHOLD()){
//如果猴子已经拿到香蕉直接展示
//对于猴子拿到香蕉却没有站在箱子上的不合法输入在读取文件时报警
StdDraw.show();
}
else if(ON.isON()){
//如果猴子没拿到香蕉但站在箱子上
//则观察箱子是否和香蕉在同一个位置
if(monkey.banana.X!= monkey.X){
//如果不在同一个位置,需要猴子先
//下箱子,推箱子到香蕉位置,爬箱子
monkey.climbDown();
monkey.pushToBanana();
monkey.climbOn();
}//然后拿到香蕉。如果在同一个位置直接拿香蕉
monkey.grasp();
}else{//如果猴子没有站在箱子上
//要观察箱子是否和猴子在同一个位置,
//以及箱子是否和香蕉在同一个位置
if(monkey.box.X!= monkey.X){
if(monkey.box.X!=monkey.banana.X){
//如果猴子和箱子不在同一个位置,箱子和香蕉也不在同一个位置
monkey.moveToBox();//猴子需要走到箱子处
monkey.pushToBanana();//推箱子到香蕉处
monkey.climbOn();//爬上箱子
monkey.grasp();//拿香蕉
}else{//如果猴子和箱子不在同一个位置,箱子和香蕉在同一个位置
monkey.moveToBox();//猴子只要走到箱子处,不用推箱子
monkey.climbOn();//直接爬箱子
monkey.grasp();//拿香蕉
}
}
else{//如果猴子没有在箱子上但和箱子在同一个位置
if(monkey.box.X!=monkey.banana.X){
//猴子推箱子到香蕉处
monkey.pushToBanana();
}//爬上箱子
monkey.climbOn();
monkey.grasp();//拿香蕉
}
}
}
}
最后,运行的效果如下
用到的lib,In和StdDraw