公司技术节搞了个 tank ai 比赛, 喜获冠军, 奖品是5000块的大疆无人机.
这也是我第一次写AI机器人. 一些技术分享一下.
当场上只有自己时, 对手会随机位置带护盾复活, 所以想到绕石转圈, 既能躲又能打. 约定 O O 为石头圆心, r r 为石头半径, A A 为tank圆心.
做法有两个:
设切点为B.
scipy.optimize import fsolve
求解.
def get_point_of_contact(O: Point, A: Point, r: float):
"""
拿切点
:param O: 圆心
:param A: 圆外一点
:param r: 圆的半径
:return: 切点坐标
"""
x0 = (A.x, A.y)
# 第一象限
if A.x > O.x and A.y < O.y:
x0 = (O.x, O.y - 1)
# 第二象限
elif A.x < O.x and A.y < O.y:
x0 = (O.x - 1, O.y)
# 第三象限
elif A.x < O.x and A.y > O.y:
x0 = (O.x, O.y + 1)
# 第四象限
else:
x0 = (O.x + 1, O.y)
x, y = fsolve(func=equations, x0=x0, args=(O, A, r))
return Point(x, y)
走到石头后计算 OA→ O A → 在地图中的角度, 然后 +90 度为接下来一步(100 ms)的行进方向.
该方法听起来很美好, 不用解二次方程组求切点. 但试验下来就会发现, 因为tank走了一步, 会远离石头, 那么接下来的垂线方向与真实的切线方向就有偏差了. 多走几步, 就是棒棒糖花纹这样的效果:
figure 像棒棒糖的花纹一样, 路径会逐渐远离圆心
记忆化回溯逃生的代码见下:
# state_arr[step][x][y] 在step步后, 若tank位于(x,y)位置, 是否存在一条生路
# -> ,↓,←,↑,keep
DIRECTION_INDEX = [(1, 0), (0, 1), (-1, 0), (0, -1), (0, 0)]
def _search_path(self, map: Map, state_arr, x: int, y: int, step: int, path: List[float]) -> int:
# 找到路啦
if step > MAX_STEP:
return SAFE
# 记忆化搜索
if state_arr[step][OFFSET_AXIS + x][OFFSET_AXIS + y] != UN_REACHED:
return state_arr[step][OFFSET_AXIS + x][OFFSET_AXIS + y]
# 越界/障碍物/是否会被子弹打中
if not is_valid or not is_safe:
state_arr[step][OFFSET_AXIS + x][OFFSET_AXIS + y] = DANGER
return DANGER
final_state = DANGER
for i in range(len(DIRECTION_INDEX)):
# 动态评估优先的逃生方向
delta_x,delta_y = get_best_survive_direction(DIRECTION_INDEX)
state = self._search_path(map, state_arr, x + delta_x, y + delta_y, step + 1, get_new_path(delta_x,delta_y))
if state == SAFE:
final_state = SAFE
state_arr[step][OFFSET_AXIS + x][OFFSET_AXIS + y] = final_state
return final_state
打出来的日志是这个样子:
2018-04-20 12:28:07,937 - yichu_survive_utils - INFO - ('survive_angle_path is: ', [3.141592653589793, 4.71238898038469, 3.141592653589793, 4.71238898038469, -1, -1, -1, -1, -1, -1, -1, -1, 4.71238898038469])