Python Robot 综合练习 (十一)

修正后的版本。

控制迷宫寻宝机器人

在这个项目中,你将使用刚刚学到的知识,尝试根据要求,编写代码,来控制一个机器人,在模拟环境中行走,并找到目标宝藏。

机器人所在的模拟环境中,会包含这样几个因素:机器人的起点、障碍物、宝藏箱。你的任务包括:

  1. 分析模拟环境的数据
  2. 控制机器人随机行动
  3. (可选)控制机器人走到终点
  • 一个良好的含有注释的代码,可以让你的程序可读性更高,尝试为你自己的代码添加相应的注释。


第一节 分析模拟环境的数据

首先,只有足够了解机器人所在的环境,我们的机器人才能成功找到目标宝藏,因此首先我们来对机器人所在环境的数据进行分析。在这个部分,会考察你对数据结构、控制流的了解。

1.1 理解模拟环境数据的储存格式

首先我们思考这样的问题:如何存储模拟环境的数据呢?

我们将我们的模拟环境抽象成一个格子世界,每个格子按照坐标编号进行标记;每个格子中会有四个情况,分别为普通格子(可通行)、机器人的起点(可通行)、障碍物(不可通行)、宝藏箱(目标点)。例如,一个模拟环境就可以抽象成3行4列的格子世界,并按这按这样的方法进行存储:

environment = [[0,0,0,2], 
               [1,2,0,0],
               [0,2,3,2]]

我们用了一个列表来保存虚拟世界的数据。外层列表中的每一个元素依然是一个列表,它代表模拟环境中每行的数据。而对于这个列表中的每个元素都是一个数,它们的含义是:

  • 0: 普通格子(可通行)
  • 1: 机器人的起点(可通行)
  • 2: 障碍物(不可通行)
  • 3: 宝藏箱(目标点)

那么,根据上述的数据,这个迷宫的第二行第一列,是我们机器人的起点。

注:我们描述的迷宫的坐标位置(第一行第一列),和迷宫下标索引的值(如 (0,0))是不一样的,请注意下标的问题。

如下的代码,使用了辅助函数,读取了模拟环境的数据,并保存在了 env_data 变量中。

import helper

env_data = helper.fetch_maze()
print(env_data)
maze-id 1-1544451683
[[3, 2, 2, 2, 2, 2, 2, 2, 1],
 [0, 0, 2, 2, 2, 2, 2, 0, 0],
 [2, 0, 0, 2, 2, 2, 0, 0, 2],
 [2, 2, 0, 0, 2, 0, 0, 2, 2],
 [2, 2, 2, 0, 0, 0, 2, 2, 2]]
[[3, 2, 2, 2, 2, 2, 2, 2, 1], [0, 0, 2, 2, 2, 2, 2, 0, 0], [2, 0, 0, 2, 2, 2, 0, 0, 2], [2, 2, 0, 0, 2, 0, 0, 2, 2], [2, 2, 2, 0, 0, 0, 2, 2, 2]]

任务1:在如下代码中,请写代码获得这些值:

  1. 模拟环境的长和宽
  2. 模拟环境中第3行第6列元素
#TODO 1模拟环境的行数
rows = None 
rows = len(env_data)
# print(rows)

#TODO 2模拟环境的列数
columns = None
columns = len(env_data[0])

#TODO 3取出模拟环境第三行第六列的元素
row_3_col_6 = None
row_3_col_6 = env_data[2][5]

print("迷宫共有", rows, "行", columns, "列,第三行第六列的元素是", row_3_col_6)
5
迷宫共有 5 行 9 列,第三行第六列的元素是 2

1.2 分析模拟环境数据

接着我们需要对模拟环境的中的数据进行分析。请根据如下的指示,计算相应的值。


任务2:在如下代码中,请计算模拟环境中,第一行和第三列的障碍物个数。

提示:可以用循环完成。

#TODO 4计算模拟环境中,第一行的的障碍物个数。
number_of_barriers_row1 = None

for item in env_data[0]:
    if item == 2:
        if number_of_barriers_row1 == None:
            number_of_barriers_row1 = 0
        number_of_barriers_row1 += 1

#TODO 5计算模拟环境中,第三列的的障碍物个数。
number_of_barriers_col3 = None
for index in range(rows):
    for i,item in enumerate(env_data[index]):
        if i == 2 and item == 2:
            if number_of_barriers_col3 == None:
                number_of_barriers_col3 = 0
            number_of_barriers_col3 += 1

print("迷宫中,第一行共有", number_of_barriers_row1, "个障碍物,第三列共有", number_of_barriers_col3, "个障碍物。")
迷宫中,第一行共有 7 个障碍物,第三列共有 3 个障碍物。
%run -i -e test.py RobotControllortTestCase.test_cal_barriers
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

任务3:在如下代码中:

  1. 创建一个名为 loc_map 的字典,它有两个键值,分别为 startdestination,对应的值分别为起点和目标点的坐标,它们以如 (0,0) 的形式保存为元组。
  2. 从字典中取出 start 对应的值,保存在 robot_current_loc 对应的变量中,这个变量表示小车现在的位置。
# 定义起点终点常量
START_VAL = 1
END_VAL = 3

# 获取机器人起点、终点坐标
def get_location(world, value):

     """
     获取机器人的位置
     world -- list 虚拟位置
     value -- 位置值
     """
     for x in range(rows):
        for y in range(columns):
            if world[x][y] == value:
                return(x, y)

# TODO 6按照上述要求创建字典
loc_map = {
    'start':get_location(env_data, START_VAL),
    'destination':get_location(env_data, END_VAL)
} 

robot_current_loc = None #TODO 7保存机器人当前的位置
robot_current_loc = loc_map['start']
%run -i -e test.py RobotControllortTestCase.test_cal_loc_map
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK


第二节 控制机器人随机漫步

在这一步中,你需发出指令,控制机器人在环境中随机行动。它会考察你对控制流、调用函数的知识。

2.1 控制机器人行动

我们的机器人能够执行四个动作:向上走 u、向下走 d、向左走 l、向右走 r。但是,由于有障碍,很多时候机器人的行动并不能成功。所以在这里,你需要实现一个函数,来判断机器人在某个位置,执行某个移动动作是否可行。


任务4:在下方代码中,实现名为 is_move_valid_special 的函数,它有两个输入,分别为机器人所在的位置坐标 loc,以及即将执行的动作 act,如 (1,1)u。接着它的返回是一个布尔值,表明小车在 loc 位置下,是否可以执行动作 act

提示1:可以读取上方定义的 env_data 变量,来读取模拟环境的数据。

提示2:在实现函数后,请删去下方的 pass 代码。

提示3:我们需要处理边界的情况,即机器人走到了虚拟环境边界时,是不能够走出虚拟环境的。

def is_move_valid_special(loc, act):
    """
    Judge wether the robot can take action act
    at location loc.

    Keyword arguments:
    loc -- tuple, robots current location
    act -- string, robots meant action
    """

    row_loc = loc[0] # 当前位置行号
    col_loc = loc[1] # 当前位置列号

    #TODO 8
    if act == 'u':
            if (row_loc - 1 >= 0) and (env_data[row_loc -1][col_loc] != 2):
                return True
            else:
                return False     
    elif act == 'd':
         if (row_loc + 1 <= len(env_data) - 1) and (env_data[row_loc + 1][col_loc] != 2):
             return True
         else:
             return False
    elif act == 'l':
         if (col_loc - 1 >= 0) and (env_data[row_loc][col_loc - 1] != 2):
             return True
         else:
             return False
    elif act == 'r':
         if col_loc + 1 <= len(env_data[0]) - 1 and (env_data[row_loc][col_loc + 1] != 2):
             return True
         else:
             return False
    else:
         return False

# print(is_move_valid_special((0,0), 'r'))
%run -i -e test.py RobotControllortTestCase.test_is_move_valid_special
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

任务5:在下方代码中,重新实现一个名为 is_move_valid 的函数,它有三个输入,分别为模拟环境的数据 env_data、机器人所在的位置坐标 loc、以及即将执行的动作 act。它的返回值与此前一样,是一个布尔值,表明小车在给定的虚拟环境中的 loc 位置下,是否可以执行动作 act

def is_move_valid(env_data, loc, act):
    """
    Judge wether the robot can take action act
    at location loc.

    Keyword arguments:
    env -- list, the environment data
    loc -- tuple, robots current location
    act -- string, robots meant action
    """
    row_loc = loc[0] 
    col_loc = loc[1] 

    #TODO 9
    if act == 'u':
            if (row_loc - 1 >= 0) and (env_data[row_loc -1][col_loc] != 2):
                return True
            else:
                return False     
    elif act == 'd':
         if (row_loc + 1 <= len(env_data) - 1) and (env_data[row_loc + 1][col_loc] != 2):
             return True
         else:
             return False
    elif act == 'l':
         if (col_loc - 1 >= 0) and (env_data[row_loc][col_loc - 1] != 2):
             return True
         else:
             return False
    elif act == 'r':
         if col_loc + 1 <= len(env_data[0]) - 1 and (env_data[row_loc][col_loc + 1] != 2):
             return True
         else:
             return False
    else:
         return False
%run -i -e test.py RobotControllortTestCase.test_is_move_valid
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

任务6:请回答:

  1. 在任务4及任务5中的实现的两个函数中,env_data 这个变量有什么不同?
  2. 调用is_move_valid函数,参数为env_data_loc_act_,如果在函数内修改env_data是否会改变env_data_的值?为什么?

提示:可以尝试从变量作用域的角度回答该问题1。

提示:可以尝试从可变类型变量和不可变类型变量的角度回答该问题2。

回答:
1、它们的区别在于前者是全局变量,后者是局部变量。
Python 同多数编程语言一样,也有全局变量与局部变量的概念,两者的本质区别就是在于作用域。
简单来说,全局变量是在全局范围内都可以访问,如果要修改它需要加上global字段,而局部变量是在某个函数中声明的,只能在该函数中调用它。

2、Python的每个对象都分为可变和不可变,主要的核心类型中,数字、字符串、元组是不可变的,列表、字典是可变的。可变参数作为参数是传引用到函数,所以在函数中对形参的修改会改变实参的值


2.2 机器人可行动作


任务7:编写一个名为 valid_actions 的函数。它有两个输入,分别为虚拟环境的数据 env_data,以及机器人所在的位置 loc,输出是一个列表,表明机器人在这个位置所有的可行动作。

提示:可以尝试调用上方定义的is_move_valid函数。

## TODO 10 从头定义、实现你的函数
def valid_actions(env_data, loc):
    """
    Judge wether the robot can take action act
    at location loc.

    Keyword arguments:
    env_data -- list, the environment data
    loc -- tuple, robots current location
    """

    acts = [] # 可移动的位置
    all_acts = ['u','d','l','r']

    for act in all_acts:
        is_move = is_move_valid(env_data, loc, act)
        if is_move == True:
            acts.append(act)

    return acts
%run -i -e test.py RobotControllortTestCase.test_valid_actions
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

2.3 移动机器人

当机器人收到一个动作的时候,你机器人的位置应发生相应的变化。

任务8:编写一个名为 move_robot 的函数,它有两个输入,分别为机器人当前所在的位置 loc 和即将执行的动作 act。接着会返回机器人执行动作之后的新位置 new_loc

##TODO 11 从头定义、实现你的函数
def move_robot(loc, act):
    """
    move robot

    Keyword arguments:
    act -- list, the environment data
    loc -- tuple, robots current location
    """

    # 新的位置
    new_loc = () 

    row_loc = loc[0]
    col_loc = loc[1]

    if act == 'u':
        new_loc = (row_loc - 1, col_loc)
    elif act == 'd':
        new_loc = (row_loc + 1, col_loc)
    elif act == 'l':
        new_loc = (row_loc, col_loc - 1)
    elif act == 'r':
        new_loc = (row_loc, col_loc + 1)

    return new_loc
%run -i -e test.py RobotControllortTestCase.test_move_robot
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

2.4 随机移动机器人

接着,我们尝试在虚拟环境中随机移动机器人,看看会有什么效果。

任务9:编写一个名为 random_choose_actions 的函数,它有两个输入,分别为虚拟环境的数据 env_data,以及机器人所在的位置 loc。机器人会执行一个300次的循环,每次循环,他会执行以下任务:

  1. 利用上方定义的 valid_actions 函数,找出当前位置下,机器人可行的动作;
  2. 利用 random 库中的 choice 函数,从机器人可行的动作中,随机挑选出一个动作;
  3. 接着根据这个动作,利用上方定义的 move_robot 函数,来移动机器人,并更新机器人的位置;
  4. 当机器人走到终点时,输出“在第n个回合找到宝藏!”。

提示:如果机器人无法在300个回合内找到宝藏的话,试试看增大这个数字,也许会有不错的效果 :P

##TODO 12 从头实现你的函数
import random
def random_choose_actions(env_data, robot_current_loc):
    for i in range(500):
        print("第{}个回合...".format(i))
        acts = valid_actions(env_data, robot_current_loc)
        act = random.choice(acts)
        is_move = is_move_valid(env_data, robot_current_loc, act)
        if is_move == True:
            new_loc = move_robot(robot_current_loc, act)
            robot_current_loc = new_loc
            if env_data[new_loc[0]][new_loc[1]] == 3:
                print("在第{}个回合找到宝藏!".format(i))
                break
# 运行
random_choose_actions(env_data, robot_current_loc)
第0个回合...
第1个回合...
第2个回合...
第3个回合...
第4个回合...
第5个回合...
第6个回合...
第7个回合...
第8个回合...
第9个回合...
第10个回合...
第11个回合...
第12个回合...
第13个回合...
第14个回合...
第15个回合...
第16个回合...
第17个回合...
第18个回合...
第19个回合...
第20个回合...
第21个回合...
第22个回合...
第23个回合...
第24个回合...
第25个回合...
第26个回合...
第27个回合...
第28个回合...
第29个回合...
第30个回合...
第31个回合...
第32个回合...
第33个回合...
第34个回合...
第35个回合...
第36个回合...
第37个回合...
第38个回合...
第39个回合...
第40个回合...
第41个回合...
第42个回合...
第43个回合...
第44个回合...
第45个回合...
第46个回合...
第47个回合...
第48个回合...
第49个回合...
第50个回合...
第51个回合...
第52个回合...
第53个回合...
第54个回合...
第55个回合...
第56个回合...
第57个回合...
第58个回合...
第59个回合...
第60个回合...
第61个回合...
第62个回合...
第63个回合...
第64个回合...
第65个回合...
第66个回合...
第67个回合...
第68个回合...
第69个回合...
第70个回合...
第71个回合...
第72个回合...
第73个回合...
第74个回合...
第75个回合...
第76个回合...
第77个回合...
第78个回合...
第79个回合...
第80个回合...
第81个回合...
第82个回合...
第83个回合...
第84个回合...
第85个回合...
第86个回合...
第87个回合...
第88个回合...
第89个回合...
第90个回合...
第91个回合...
第92个回合...
第93个回合...
第94个回合...
第95个回合...
第96个回合...
第97个回合...
第98个回合...
第99个回合...
第100个回合...
第101个回合...
第102个回合...
第103个回合...
第104个回合...
第105个回合...
第106个回合...
第107个回合...
第108个回合...
第109个回合...
第110个回合...
第111个回合...
第112个回合...
第113个回合...
第114个回合...
第115个回合...
第116个回合...
第117个回合...
第118个回合...
第119个回合...
第120个回合...
第121个回合...
第122个回合...
第123个回合...
第124个回合...
第125个回合...
第126个回合...
第127个回合...
第128个回合...
第129个回合...
第130个回合...
第131个回合...
第132个回合...
第133个回合...
第134个回合...
第135个回合...
第136个回合...
第137个回合...
第138个回合...
第139个回合...
第140个回合...
第141个回合...
第142个回合...
第143个回合...
第144个回合...
第145个回合...
第146个回合...
第147个回合...
第148个回合...
第149个回合...
第150个回合...
第151个回合...
第152个回合...
第153个回合...
第154个回合...
第155个回合...
第156个回合...
第157个回合...
第158个回合...
第159个回合...
第160个回合...
第161个回合...
第162个回合...
第163个回合...
第164个回合...
第165个回合...
第166个回合...
第167个回合...
第168个回合...
第169个回合...
第170个回合...
第171个回合...
第172个回合...
第173个回合...
第174个回合...
第175个回合...
第176个回合...
第177个回合...
第178个回合...
第179个回合...
第180个回合...
第181个回合...
第182个回合...
第183个回合...
第184个回合...
第185个回合...
第186个回合...
第187个回合...
第188个回合...
第189个回合...
第190个回合...
第191个回合...
第192个回合...
第193个回合...
第194个回合...
第195个回合...
第196个回合...
第197个回合...
第198个回合...
第199个回合...
第200个回合...
第201个回合...
第202个回合...
第203个回合...
第204个回合...
第205个回合...
第206个回合...
第207个回合...
第208个回合...
第209个回合...
第210个回合...
第211个回合...
第212个回合...
第213个回合...
第214个回合...
第215个回合...
第216个回合...
第217个回合...
第218个回合...
第219个回合...
第220个回合...
第221个回合...
第222个回合...
第223个回合...
第224个回合...
第225个回合...
第226个回合...
第227个回合...
第228个回合...
第229个回合...
第230个回合...
第231个回合...
第232个回合...
第233个回合...
第234个回合...
第235个回合...
第236个回合...
第237个回合...
第238个回合...
第239个回合...
第240个回合...
第241个回合...
第242个回合...
第243个回合...
第244个回合...
第245个回合...
第246个回合...
第247个回合...
第248个回合...
第249个回合...
第250个回合...
第251个回合...
第252个回合...
第253个回合...
第254个回合...
第255个回合...
第256个回合...
第257个回合...
第258个回合...
第259个回合...
第260个回合...
第261个回合...
第262个回合...
第263个回合...
第264个回合...
第265个回合...
第266个回合...
第267个回合...
第268个回合...
第269个回合...
第270个回合...
第271个回合...
第272个回合...
第273个回合...
在第273个回合找到宝藏!

为者常成,行者常至