斯坦福编程方法学作业讲解4--Karel机器人三大定律 (下)

如何适应所有的地图?

我们还是继续完成上一期遗留的任务:如何设计适应所有地图的程序?如果我们自己是Karel机器人的时候,我们如何去拥抱这个未知的世界?我们是不是一直往前走,只要前面有路,我们就不断往前,那什么时候停止了?直到我们遇到了墙为止。那我们程序如何对应实现这个思想了?我们回忆一下讲循环的时候,是不是提到循环,有两种方式,一种是for 循环,for语句用于当你想按预定的次数重复执行一组命令的时候,while语句用于当你想在某些条件满足时候,重复执行一组命令的时候。好像while可以解决我们的问题。我们看看while的语法

while(条件){

    重复执行的语句。

}

当条件被满足的时候,就一直执行大括号里面的命令。

我们依然将任务先分解一下,先完成放满一行的程序,如何完成放满一行的程序了?while里的条件是什么?

当前面道路是通的话,Karel继续往前走,那么while里的条件是前面道路是不是通的?如果条件是通的,就一直执行循环体里的内容。那如何检测前面道路是不是通的,系统提供了一系列检测条件:frontIsClear()检测前面道路是不是通的,如果是通的就表示条件为真,不通表示条件为假。在while循环里,使用frontIsClear()作为检测条件,karel将重复执行循环命令,直到它前面遇到墙,循环结束。这样就可以适用所有地图。

我们还是先完成填满一行的命令:

	public  void  createBeeperLine(){
		while(frontIsClear()){
			putBeeper();
			move();
		}
	}

 

大家觉得是不是能完成我们的目标,我们现在已经开始有意识到off by one的错误。当前面有墙的时候,Karel就不执行循环体里的内容,不往前走,但是也不要忘了在当前位置放下方块。那我们很容易解决这个问题:

	public  void  createBeeperLine(){
		while(frontIsClear()){
			putBeeper();
			move();
		}
		putBeeper();
	}

这样填满一行的问题解决了,那么我们接下来要完成所有行如何去做?

跟放满一行类似,我们不知道具体的行数,我们使用while函数,while里的判断条件依然是前面是不是道路是不是通的?如何判断是不是前面道路是不是通的?用frontIsClear()命令可不可以?这个时候我们要注意Karel的朝向,必须把它调整到面朝北向,也就是朝上,我们才能使用frontIsClear()判断上面是不是还有新的一行。

系统有没有提供像frontIsClear()这样的命令直接判断上面是不是还有一行了?很不幸,没有,宝宝不开心啦….我们能不能自己写一个这样的命令,现在系统提供了如下的命令:

leftIsClear()

leftIsBlocked()

左边是不是有墙?

rightIsClear()

rightIsBlocked()

右边是不是有墙

我们结合上一次的命令:

facingEast()

notFacingEast()

Karel是不是面朝东方的

facingWest():

notFacingWest():

Karel是不是面朝西方的

facingNorth()

notFacingNorth()

Karel是不是面朝北方的

facingSouth()

notFacingSouth()

Karel是不是面朝南方的

我们是不是自己能封装一个northIsClear和northIsBlocked的命令了:

我们看看如果karel现在面朝向东,而它的左手边是通的话,那么它的上边就是通的。如果Karel现在朝西的话,而它的右手边是通的话,那么它的上边就是通的,否则就是不通的。大家一定要搞清楚东西南北的方向,千万不要是一个路痴。我们如何去实现这样的一个命令,我们之前只实现过像右转,或者放一行的命令。我们并没有实现过这种带着开放性的命令,判断一件事是不是正确。我们如何完成这种像问答式的命令了。

我们这个时候再回忆我们再写方法的时候的格式:

public  void  run(){

  方法体;

}

我们当时说方法体的头两个单词public  void是Java的格式,我们先忽略它们。现在我们看看第二个单词的作用:方法的返回值。java中方法,其实就是执行一个动作的。比如“调用XX方法计算年收入”、 “调用XX方法打印出学生学号”,这些方法都是有一个执行目的,每一个方法都是为一个执行目的而生的,返回值就与那个方法的目的有关。

比如我们调用XX方法计算年收入,我们的目的是得到全年的收入,那我们需要那个方法完成两件事:一是计算年收入并且返回收入值给我们,收入值就是这个方法的返回值,这个返回值的数据类型就是方法的返回类型,比如这里,我们定义为double(小数型)。

而有的方法我们不需要他返回什么值,比如我们之前调用向左转命令turnLeft(),我们不需要得到任何反馈,只需要他做就行了,于是就定义这个方法的返回类型为void,意思是没有返回值。

所以,其实方法的返回类型就是他返回的那个数据的类型,如果不返回任何数据,就是void!我们之前使用的方法使用void就意味着不需要返回任何数据,而仅仅需要做那件事。

那么现在我们需要返回一个逻辑判断的数据类型,返回类型是boolean型的。只能是true(真)或false(假)两个值之一。那么northIsClear()命令的格式如下:

	public boolean northIsClear(){
		if(条件){
			return true;
		}
		else{
			return false;
		}
	}
	

现在还有一个问题是我们在上面分析的条件:Karel现在面朝向东,而它的左手边是通的话。或者朝西的话,而它的右手边是通的话。那么Karel的北向是通的,我们如何用代码去表达这样复杂的逻辑运算。我们简单的看一下逻辑运算规则:

有几个组合布尔型数值的运算符,包括:布尔与(AND),布尔或(OR)和布尔非(它们分别对应&&、||、!),以及产生boolean型结果的比较运算符。

 

逻辑运算符(Logical Operators)

操作数的逻辑关系,计算结果“true”或“false”

逻辑与  &&  “op1 && op2”

1)        操作数都为真“true”,结果为真“true”

2)        否则结果为假“false”

逻辑或  ||    “op1 || op2”

1)        有一个操作数为真“true”,结果为真“true”

2)        否则结果为假“false”

逻辑非  !     “! op”

1)        取反,操作数为真“true”,结果为真“false”,反之……

比如我们这里Karel满足面朝向东,而它的左手边是通的话,这个时候北向是通的条件就为真,用代码去表示就是:

if(facingEast()&& leftIsClear()){
		return true;
	}

 

现在我们可以完成northIsClear()的代码如下:

	
	public boolean northIsClear(){
		if (facingEast() && leftIsClear()){
			return true;
		}
		
		if (facingWest() && rightIsClear()){
			return true;
		}
		
		return false;
	}
	
	public boolean northIsBlocked(){
		if (facingEast() && leftIsClear()){
			return false;
		}
		
		if (facingWest() && rightIsClear()){
			return false;
		}
		
		return true;
	}
		

 

我们再从整体上思考一下我们如何完成任务:我们是不是每次先放满一行,然后判断上面有没有通路,有的话,就往上走一行,继续执行放满一行的命令。如何没有的话,就结束任务。对应的完整代码如下:

public class CollectNewspaperKarel extends Karel {
	
	// You fill in this part
	
	public void run(){
		boolean hasNotCompleted= true;
		while(hasNotCompleted){
			createBeeperLine();
			if (northIsBlocked()){
				hasNotCompleted = false;
			}
			
			if(hasNotCompleted){
				moveToNextRow();
			}
		}
	}
	
	public void createBeeperLine(){
		while(frontIsClear()){
			putBeeper();
			move();
		}
		
		   putBeeper();
	}

	public void moveToNextRow(){

		if (facingEast()){
			turnLeft ();
			move();
			turnLeft ();
		}
		else{
			turnRight();
			move();
			turnRight();
		}

	}


	public boolean northIsClear(){

		if (facingEast() && leftIsClear()){
			
			return true;
			
		}


		if (facingWest() && rightIsClear()){

			return true;

		}

		return false;
	}

	
	
	public boolean northIsBlocked(){
		if (facingEast() && leftIsClear()){
			return false;
		}
		
		if (facingWest() && rightIsClear()){
			return false;
		}
		
		return true;
	}
	
	
}

 

我们测试代码,是不是发现能够顺利完成任务。我们在这个任务的基础上完成新的一点挑战:在空的矩形完成跳棋盘,如下图

我们发现只需要改写createBeeperLine()就可以完成目标了,如何改写?我们仔细观察,可以发现就是隔一个放一个,我们如何用代码去实现了,我们同样可以分两种情况:

1)        如果当前位置有Beeper的话,接下来执行的步骤是什么?

2)        如果当前位置没有Beeper的话,接下来做什么?

 

我们仔细想一想很快得出结论:如果当前位置有Beeper的话,接下来移到下一个位置,下一个位置不放Beeper。

如果当前位置没有Beeper的话,那么移到下一个位置,必须放Beeper。

 

同时,我们需要记住每一行的起始位置放不放Beeper,需要事先知道。我们定义theFirstPutBeeper变量表示这一行第一个究竟放不放Beeper。

函数就行了:

	
	
	public  void  createBeeperLine(){
		
		if (theFirstPutBeeper){
			
			putBeeper();
			
		}
		while(frontIsClear()){
			
				if(beepersPresent()){
					
					move();
					
				}
				else
				{  
					move();
					putBeeper();
					
				}
				
			}
			
	}

 

我们要考虑处理theFirstPutBeeper变量,在哪一行第一个需要放,哪一行不需要放,我们怎么处理了?

其实我们在一行结束的时候,需要移动到上一行的时候,可以处理。如果当前行结束的位置有方块的话,那么下一行的开始的位置就不用放Beeper,也就是theFirstPutBeeper = false;如果当前行结束的位置没有Beeper,那么下一行开始的时候就需要放Beeper,也就是theFirstPutBeeper = true;那么我们现在需要一个判断当前位置是否有Beeper的命令。幸运的是系统提供了这个命令:

beepersPresent( ):当前位置是否有Beepers。

完整的代码如下:

public class CheckerboardKarel extends SuperKarel {

	// You fill in this part
	
	
	boolean theFirstPutBeeper = true;
	
	public void run(){

	
			boolean hasNotCompleted= true;   
	    
			while(hasNotCompleted){
	    	
				createBeeperLine();
				if (northIsBlocked()){
					hasNotCompleted = false;
				}
				if(hasNotCompleted){
					if(beepersPresent()){
			    		   theFirstPutBeeper = false;
			    		}
			    	   else{
			    		   theFirstPutBeeper = true; 
			    	   }

					moveToNextRow();
				}
			}
	    	 
	}

		  
		  
	public  void  createBeeperLine(){

			  if (theFirstPutBeeper){
					putBeeper();
			  }
			  
			  while(frontIsClear()){
				 
				  if(beepersPresent()){
						move();
						
				  }
				  else{   
					  move();
					  putBeeper();
				  }		
						
			  }


	}	 
	
	
	public void moveToNextRow(){
		  if (facingEast()){
			  turnLeft ();
			  move();
			  turnLeft ();
		  }
		  else
		  {
			  turnRight();
			  move();
			  turnRight();
		  }
	  } 
	
	  public boolean northIsClear(){
		  if (facingEast() && leftIsClear()){
			  return true;
		  }
		  
		  if (facingWest()  && rightIsClear()){
			  return true;
		  }
		  
		  return false;
		  
	  }
	  
	  public boolean northIsBlocked(){
		  if (facingEast() && leftIsClear()){
			  return false;
		  }
		  
		  if (facingWest()  && rightIsClear()){
			  return false;
		  }
		  
		  return true;
		  
	  }
	  
	  
	

	

}

 

我们发现相比较上一个任务,我们就只稍微改动了一点点就完成了这个任务。我们是不是体会到了,好的代码设计,非常容易扩展,适应变化的需求。当需求发现变化的时候,我们能够很快地扩展了原来的应用。 我们发现两者的代码非常接近,除了createBeeperLine()稍微不一样,其他基本一致,我们有没有办法,只用一套代码实现两种任务。这个任务稍微有点挑战,等以后大家学习了Java的三大特性之一的多态,就很容易处理。等那个时候,我们再来解决这个问题,体现面向对象的强大。

 

在完成整个代码的过程中,我们不仅对循环,判断语法变得熟悉起来,也体会到将任务不断分解,不仅让逻辑变得清晰,结构合理,容易扩展,而且出错的几率也相对少。我们下一节继续探讨任务的分解,欢迎收看下一期课程:Karel的化整为零大法。

转载于:https://my.oschina.net/u/2398237/blog/867308

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
6-UCU结构并联机器人是由6个旋转关节和3个平移关节组成的机器人,其中旋转关节用U表示,平移关节用C表示。该机器人的运动学方程可以通过前向运动学和逆向运动学两种方法求解。 一、前向运动学方程 前向运动学是指已知机械臂各关节的运动状态,求出末端执行器的位置和姿态信息。6-UCU结构并联机器人的前向运动学方程可以表示为: T06 = T01 * T12 * T23 * T34 * T45 * T56 其中,Tij表示从第i个关节到第j个关节的变换矩阵,i和j的取值分别为0~6,其中0表示机器人基座的位置,6表示机器人末端执行器的位置。具体的变换矩阵可以根据机器人的结构和参数进行计算。 二、逆向运动学方程 逆向运动学是指已知末端执行器的位置和姿态信息,求出各个关节的运动状态。对于6-UCU结构并联机器人的逆向运动学方程,可以采用解析法和数值法两种方法进行求解。 1. 解析法 解析法是通过数学公式直接求解逆向运动学方程。对于6-UCU结构并联机器人,可以采用解析法求解其逆向运动学方程。具体的求解过程可以参考相关的数学文献。 2. 数值法 数值法是通过迭代计算的方式逐步逼近逆向运动学方程的解。对于6-UCU结构并联机器人,可以采用数值法求解其逆向运动学方程。具体的求解过程可以采用牛顿-拉夫森迭代法等常见的数值算法进行计算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值