Lecture 03 · Movement, Coordinates, and Evasion

坦克的大脑

用 Coordinate(坐标)、Angle(角度)与 Event(事件)理解战场,并写出更不容易被预测的移动策略。

Coordinate(坐标) Angle(角度) Loop(循环) Wall Collision(撞墙)

从“会开火”到“会生存”

前两课我们让坦克会动、会看、会开火;这一课开始训练策略判断:为什么被打中,为什么撞墙,下一步怎么改。

观察现象

被连续击中、经常撞墙、路线太固定,都是可分析的战场信号。

回到代码

把现象对应到移动距离、转向角度、循环与事件处理器。

小步实验

一次只改一个机制,测试后再判断是否真的改善。

今天的学习目标

理解战场

认识 Robocode Tank Royale 的坐标系统:左下角是 (0, 0)

改善移动

使用 Loop(循环)与随机变化,让移动不再像固定路线一样容易预测。

处理事件

在受击或撞墙时触发反应,把危险变成策略调整机会。

2 小时课堂节奏

0:00 - 0:15

复习扫描与火力规则,观察上一版 Bot 的弱点。

0:15 - 0:40

讲解坐标、角度、边界与撞墙。

0:40 - 1:10

教师示范:受击反应、撞墙反应、随机走位。

1:10 - 2:00

学生实作、对战测试、Debug 与保存版本。

战场坐标怎么读?

  • Robocode Tank Royale 使用 Cartesian Coordinate System(笛卡尔坐标系)。
  • (0, 0) 在战场左下角。
  • x 越大越靠右,y 越大越靠上。
  • Bot 的位置以坦克中心点计算。

参考: Coordinates and Angles

战场坐标系示意图:左下角为 (0,0),x 向右、y 向上

读取自己的位置

@Override
public void run() {
    while (isRunning()) {
        double myX = getX();
        double myY = getY();
        double myDirection = getDirection();

        forward(100);
        turnGunLeft(360);
    }
}

今天不要求大家做复杂数学;重点是知道代码可以读取当前位置,并用它辅助判断。

移动的物理规则

记住几条规则,就能解释坦克为什么“转不动”、为什么撞墙很痛:

  • 最高速度为 8 单位/回合;加速每回合 +1,刹车每回合 −2(刹车比加速快)。
  • 转身速率 = 10 − 0.75 × |速度|:开得越快,每回合能转的角度越小。
  • 撞墙伤害 = |速度| / 2 − 1:速度越高,撞墙越痛。

策略含义

想灵活转向躲避,就别一直全速直线冲;接近墙时先放慢,能少受伤,也更难被对手预测。

参考: Robocode Physics

为什么固定路线容易输?

如果坦克永远前进 100、后退 100、再转同样角度,对手很容易预测下一步位置。策略优化的第一步,是减少固定规律。

  • 改变移动距离。
  • 改变转向角度。
  • 在被击中后立刻换方向。

随机走位:让路线不那么固定

@Override
public void run() {
    while (isRunning()) {
        if (Math.random() < 0.5) {
            turnLeft(35);
        } else {
            turnRight(35);
        }

        forward(120);
        turnGunLeft(360);
    }
}

Math.random() 会产生 0 到 1 之间的小数,用来制造简单的不确定性。

受击事件:被打中后不要继续原路线

@Override
public void onHitByBullet(HitByBulletEvent e) {
    turnRight(90);
    forward(120);
}

策略含义

被打中代表当前路线可能被敌人瞄准。立刻转向并移动,能降低继续被命中的机会。

更聪明的躲避:垂直于子弹

与其随便转 90 度,不如转到与子弹来向垂直的方向,向侧面移动,让对手更难再次命中:

  • e.getBullet().getDirection():打中我们的子弹的飞行方向。
  • calcBearing(方向):把一个绝对方向换算成相对车身的角度(助手方法)。
  • 90 - bearing:转到与子弹垂直的方向。
@Override
public void onHitByBullet(HitByBulletEvent e) {
    double bearing = calcBearing(e.getBullet().getDirection());
    turnRight(90 - bearing);
    forward(100);
}

这正是模板里 MyFirstBot 被击中时使用的躲避方式。

撞墙事件:从错误现象回到代码

@Override
public void onHitWall(HitWallEvent e) {
    back(80);
    turnRight(90);
}
  • 撞墙不是“电脑坏了”,而是移动策略没有考虑边界。
  • 先退后转,比继续往同一个方向走更合理。
  • 观察撞墙频率,可以判断移动距离是否太长。

阻塞与非阻塞:让坦克边走边转

到目前为止我们用的都是阻塞 (blocking) 指令:

  • forward(100)一直等到走完,才执行下一行。
  • 好处是直观;代价是同一时间只能做一件事。

同时移动并转向,可改用 set 系列 + go()

setForward(10000);
setTurnLeft(90);
go(); // 一起执行:边走边转

模板里的 SpinBotCrazy 就是这样实现边走边转的。

教师示范:三层改进

  1. 先运行固定路线,记录最常发生的问题。
  2. 加入 onHitByBullet(...),观察受击后是否更快离开原位置。
  3. 加入 onHitWall(...) 和随机转向,观察生存时间是否改善。

每次只加入一个机制,才能知道改善来自哪里。

学生练习:设计一个避弹节奏

基础任务

  • 加入一个随机转向规则。
  • 加入 onHitByBullet(...) 的受击反应。
  • 加入 onHitWall(...) 的撞墙反应。
  • 至少测试三场,并记录最常见失败原因。

进阶挑战

如果靠近战场边缘,就减少前进距离或增加转向角度。

进阶示例:靠近边缘时缩短前进

double margin = 100;
double moveDistance = 120;

// 离任何一道墙太近,就少走一点
if (getX() < margin || getY() < margin
        || getX() > getArenaWidth() - margin
        || getY() > getArenaHeight() - margin) {
    moveDistance = 60;
}
forward(moveDistance);

getArenaWidth()getArenaHeight() 判断四面墙,比只看 (0, 0) 那个角更可靠。

常见错误

  • 忘记事件类型:HitByBulletEventHitWallEvent 都来自 events package(事件包)。
  • 受击后移动太少:转向了但没有离开原位置,仍可能继续被命中。
  • 随机过度:完全随机不等于好策略,仍需要观察结果。
  • 撞墙后继续撞:后退距离太短或转向角度太小。
  • 一次改太多:无法判断是哪一段代码造成新的行为。

Debugging(调试)记录表

可拍照或打印;实作时填写自己的一行记录。

现象 可能原因 小步修正
例:连续被击中 例:路线太固定 例:加入 onHitByBullet 转向

集体 Debug:故意写错的 Bot

模板仓库的 BrokenWallBot 在撞墙后没有正确后退(故意写错)。用 5 分钟集体读代码,找出 onHitWall(...) 的问题。

参考: 学生代码模板

课堂反思与课后延伸

  • 你的坦克最常输在攻击不足,还是生存不足?
  • 随机走位是否真的改善了结果?你如何判断?
  • 如果要让坦克更少撞墙,你会先改移动距离、转向角度,还是加入坐标判断?

建议 commit message:Add evasion and wall handling。课后延伸:把最有效的一项移动改进写进 commit message,让 Git 历史成为坦克进化记录。

授权

趣味 Java 编程启蒙班 © 2026 by Eng Zer Jun is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International