Simple AvZ ---------- 一种很新的 AvZ
Crescebdo
编辑于 2023年12月29日 08:39
收录于文集
共15篇

Simple AvZ, 即 "AvZ 简化版&#​34;, 是基于 AvZ 1 设计的一套用于精简脚本的语法框架.


使用方式

从零开始安装 AvZ 1 与 Simple AvZ 的视频教程:见此​

如果你已安装 AvZ 1 环境,只需通过 Get AvZ Extension 下载本插件的最新版本后, 在脚本中加入 #include &#​34;SimpleAvZ/lib.h" 即可.

若 Get AvZ Extension 不可用, 可从本仓库 /release 目录中手动下载. (https://gitlab.com/avzlib/AvZLib/-/tree/main/crescendo/SimpleAvZ/SimpleAvZ).


设计理念

  • 简洁 好于 冗长

  • 声明 好于 命令

  • 安全 好于 效率

下面举一些例子说明上述理念.


剔除 SetTime

Simple AvZ 的重要目标之一, 是让脚本中不再出现 SetTime 与 Delay.

以 [PE]. 最后之作 的减速波为例:

代码块
C++
自动换行
复制代码
// 原生 AvZ 1
for (auto wave : {...}) {
    SetTime(410 - 373, wave);
    pao_operator.pao({{2, 8.75}, {5, 8.75}});
    Delay(220);
    pao_operator.pao({{1, 8.575}, {5, 8.575}});
    SetTime(1100 - 378);
    pao_operator.pao(3, 8.2375);
    SetTime(1887 - 200 - 373);
    pao_operator.pao({{2, 8.5}, {5, 8.5}});
    SetTime(1887 + 10 - 298);
    ice_filler.coffee();
    SetPlantActiveTime(ICE_SHROOM, 298);
}
复制成功

如果你刚写完这段脚本, 并不会觉得有违和感.

可当一段时间后重读, 或是其他人阅读此段时, 大家关心的是具体操作及时机, 可满眼望去却都是 SetTime, Delay, 和反复出现的神秘嘉宾 -373.

使用 Simple AvZ, 以上代码简化为:

代码块
C++
自动换行
复制代码
// Simple AvZ 💫
for (auto w : waves(...)) {
    PP(410, 8.75);
    DD(after(220), 8.575);
    B(1100, 3, 8.8275);
    PP(1887 - 200, 8.5);

    I(after(210));
}
复制成功

代码行数减半, 重要信息一览无余, 且十分接近我们熟知的轨道语言, 一看就懂.

我们知道炮的飞行时间, 知道当前场景, 更知道 PE 下多数情况不是炸 2,5 就是炸 1,5 路. 既然如此, 为何不对这些信息加以利用呢? 这是 Simple AvZ 的初衷.


以生效时机思考

对手控玩家而言, 掐时机至关重要, 什么时候点冰, 什么时候拖炮都有讲究.

可在键控领域, 我们更关心生效时机!

使用 Simple AvZ, 你可以写出这样的代码:

代码块
C++
自动换行
复制代码
// Simple AvZ 💫
PP(318, 8.8);
复制成功

声明我要在 318cs 于 8.8 列生效并炸炮.

至于 PP 炸哪行? 如果有上界之风, 应如何修正发炮时间? 这一切都由 Simple AvZ 自动推测, 编写者无需多虑.

这就是所谓的声明式编程, 即以结果为导向,而非以过程为导向.

这不仅让代码更简洁易懂, 增加编写效率, 更避免了一些本不必要的 bug. 例如炮击泳池飞行 378cs, 炮击平地飞行 373cs, 若不小心区分很容易混淆, 可多数情况下我们本不关心这些, 只是想在特定时机让炮生效罢了.


基于炮的时间体系

由于游戏的蛋疼设定, 每帧结算顺序为 [植物] --> [僵尸] --> [子弹].

说来也巧, 由于冰与灰烬(樱辣窝核)属于 [植物], 而炮又属于 [子弹], 两者相对于 [僵尸] 的结算顺序便有了 1cs 偏差.

简单来说, x cs 生效的炮 = x + 1 cs 生效的卡.

这个不一致性在 AvZ 1 设计伊始就已显现, 如 AvZ::SetPlantActiveTime(), 即 Ice3 函数, 其实际效果为将冰生效时机修正为设定时机的 下一 cs.

一说这有道理, 因为按僵尸时间, 巨人确实运动了 105/210cs, 只是由于冰早于僵尸结算, 故实际要 +1cs. 一说这不科学, 其它用卡用炮皆以实际时机书写, 为何冰要特殊处理?

Simple AvZ 对此的解决思路如下.

首先,基于声明好于命令的理念, 樱辣窝核这四张卡以炮时机书写更优, 因为实际时机不如等效炮时机好用. 如此便有:

代码块
C++
自动换行
复制代码
// Simple AvZ 💫
P(359, 2, 9);
C(359, CHERRY, 5, 9);
DD(after(107), 1, 7.8);
复制成功

樱桃实际在 360 生效, 但编写者和读者无需考虑这样的细枝末节, 统一作 359 即可, 清晰易懂.

由于樱辣窝核以炮时机书写甚为合理, Simple AvZ 选择将冰也纳入此体系, 以炮为纲, 一统江湖. 这也使得其对旧脚本的兼容性更佳, 且更符合 AvZ 的设计初衷.


自动设定波次

原生 AvZ 中, 我们常用 for (auto wave : {...}) 书写循环, 但循环节内部首次 SetTime 时, 必须不能忘记带上 wave, 否则 AvZ 很生气, 后果很严重.

既然这种写法如此常用, 为何不自动设定波次呢? 于是就有了:

代码块
C++
自动换行
复制代码
// Simple AvZ 💫
for (auto w : waves(1, 2, 3)) {
    PP(318);
    ...
}
复制成功

使用 Simple AvZ 提供的 waves() 函数, 妈妈再也不担心我忘记设定波次啦.

不仅如此, 你还可以用 waves() 表达更复杂的循环:

代码块
C++
自动换行
复制代码
waves({1, 9}, 4)             // w1~w9 每 4 波一循环
waves({1, 9}, {11, 19}, 4)   // w1~w9, w11~w19 每4波一循环
复制成功


更便捷的用卡

原生 AvZ 有以下三种常用的用卡方式:

代码块
C++
自动换行
复制代码
Card(PEA_SHOOTER, 1, 1); // 在1-1种植豌豆射手
Card(PEA_SHOOTER, {{1, 1}, {1, 2}}); // 先尝试在1-1种, 不行则改为1-2
Card({{PEA_SHOOTER, 1, 1}, {SUNFLOWER, 1, 2}}); // 1-1种豌豆, 1-2种小向
复制成功

可在实战中, 我们经常需要:

代码块
C++
自动换行
复制代码
// 垫1, 2, 5, 6路
Card({{PUFF_SHROOM, 1, 9}, {SUN_SHROOM, 2, 9}, {SCAREDY_SHROOM, 5, 9}, {FLOWER_POT, 6, 9}});

// 在水上点核
Card({{LILY_PAD, 3, 9}, {DOOM_SHROOM, 3, 9}, {COFFEE_BEAN, 3, 9}});
复制成功

长久以来, 我们都习惯了这样写. 可既然这两种情况如此常见, 为何不提供更便捷的写法呢?

代码块
C++
自动换行
复制代码
// Simple AvZ 💫
C(400, {PUFF, SUN, SCAREDY, POT}, {1, 2, 5, 6}, 9); // 垫1, 2, 5, 6路
C(359, {LILY, DOOM, COFFEE}, 3, 9); // 在水上点核
复制成功

对常用的植物与僵尸, Simple AvZ 提供了(合理的)缩写.

有种即有铲. 你可以便捷地传入铲除时机:

代码块
C++
自动换行
复制代码
// Simple AvZ 💫
C(400, keep(268), PUFF, 1, 9); // 400cs种植, 268cs后铲
C(400, until(1038), PUFF, 1, 9); // 400cs种植, 于1038cs铲
复制成功

此外, 你也可以指定延迟 x cs 生效卡, 或是直接调用铲除函数等, 详见完整 API.


安全优先

不知道你是否曾手滑, 写出过下面这样的代码呢?

代码块
C++
自动换行
复制代码
// 原生 AvZ 1
pao_operator.pao(5, 8525);
复制成功

PvZ 里当然没有 8525 列, 可 AvZ 对于这样的语句全程不会报错, 徒留你在游戏里看着莫名奇妙轨迹的炮独自凌乱.

游戏设定是明确的, 发射 0.0~10.0 列之外的炮必定没有意义. 既然如此, 我们可以增加报错信息, 让你在手滑后第一时间就能发现错误.

Simple AvZ 提供的每一个函数都会对输入进行严格检查. 在 AvZ 调用 Script(), 也就是刚点进 Survival Endless 模式时, 就会告知玩家代码中的错误.

上面的代码若用 Simple AvZ 提供的 P 函数, 将报如下错:

代码块
C++
自动换行
复制代码
[调用 P 时出错]
落点列应在0.0~10.0内
落点列: 8525
复制成功

此外, Simple AvZ 会自动检测 waves() 调用的波次是否有重复的, 避免写错波次造成的不必要的麻烦.


API 一览 (v1.0.0)

设定波次

代码块
C++
自动换行
复制代码
waves(1, 2, 3);             // w1, w2, w3(可指定任意多个)
waves({1, 9}, 4);           // w1~w9 每4波一循环
waves({1, 9}, {11, 19}, 4); // w1~w9, w11~w19 每4波一循环
复制成功

代码块
C++
自动换行
复制代码
for (auto w : waves(...)) {
    // 具体操作
    // 可以使用 if 语句判断当前在哪波
    if (w == 1) {
        C(359, DOOM, 2, 9);
    } else if (w == 5) {
        C(359, DOOM, 3, 9);
    }
}
复制成功

Simple AvZ 的函数除非标注"[外]&#​34;, 都需在 waves() 循环节内部使用.

设定时间

代码块
C++
自动换行
复制代码
(318, ...)            // 于本波318cs生效
(after(110), ...)     // 延迟110cs生效
(now, ...)            // now 等价于 after(0)
复制成功

大多数 Simple AvZ 函数的第一个参数为时间. after() 可以取负值, 但不推荐这样做, 因为会降低可读性.

用炮

代码块
C++
自动换行
复制代码
PP(318);               // 炸(2,9)与(5,9), 318cs生效
PP(318, 8);            // 炸(2,8)与(5,8)
PP(318, {8, 9});       // 炸(2,8)与(5,9)
PP(318, {2, 6}, 9);    // 炸(2,9)与(6,9)
复制成功

发射一组并炸炮. 若省略行数, 六行场地炸 2,5 路, 五行场地炸 2,4 路. 若省略列数, 默认炸 9 列.

代码块
C++
自动换行
复制代码
PP(after(110), ...);   // 用法同上, 延迟110cs生效
PP(now, ...);          // now 与 after(0) 等价
复制成功

用炮生效时机的变种. 其它函数也可使用.

代码块
C++
自动换行
复制代码
DD(...);               // 用法与PP相同
复制成功

发射一组拦截炮. 若省略行数, 六行场地炸 1,5 路, 五行场地炸 1,4 路. 若省略列数, 默认炸 9 列.

代码块
C++
自动换行
复制代码
P(318, 2, 9);              // 炸(2,9), 318cs生效
B(...);                    // 分离炮, 用法与P相同
D(...);                    // 拦截炮, 用法与P相同
P(318, 1, 1, 2, 8);        // 使用1-1炮炸(2,8)
复制成功

发射一门炮. 可指明要用哪门炮. 可视情况选用 P, B, D 三种标签之一, 传达用炮意图.

代码块
C++
自动换行
复制代码
ExcludeCob(3, 5);     // 不使用3-5炮, 游戏开始时起效[外]
ExcludeCob(400, ...); // 400cs起效
ResetCob(400);        // 重置为使用所有炮, 400cs起效
复制成功

不使用特定炮, 或重置为使用所有炮.

代码块
C++
自动换行
复制代码
CobOperator c1(1);       // 只用炮尾在1列的炮
CobOperator c45(4, 5);   // 只用炮尾在4或5列的炮

void Script() {
    c1.PP(...);     // 调用方法相同
}
复制成功

在屋顶场合, 你可以声明多个 CobOperator, 然后使用以上函数.

用卡 / 用铲

冰, 核, 樱, 辣, 窝 默认使用炮等效时间, 见[基于炮的时间体系].

代码块
C++
自动换行
复制代码
I(1, 2);             // 于1-2放置原版冰, 601cs生效(完美预判冰)
I(after(210), 1, 2); // 延迟210cs生效(ice3), 推荐在激活炮后使用
I(359, 1, 2);        // 359cs生效
M_I(...);            // 用法同上, 使用复制冰
复制成功

夜间用冰. 自带生效时机修正. 若不指定生效时间, 默认在本波 601cs 生效.

代码块
C++
自动换行
复制代码
SetIce({{1, 1}, {1, 2}});  // 在1-1, 1-2存冰(优先使用1-2)[外]
SetIce(400, {...});        // 400cs生效
复制成功

白昼设置存冰位置. 若不指定生效时间, 默认在 wave 1, -599cs 生效.

代码块
C++
自动换行
复制代码
I();                       // 点冰, 601cs生效(完美预判冰)
I(after(210));             // 延迟210cs生效(ice3), 推荐在激活炮后使用
I(359);                    // 359cs生效
复制成功

白昼点冰. 自带生效时机修正. 若不指定生效时间, 默认在本波 601cs 生效.

代码块
C++
自动换行
复制代码
C(359, CHERRY, 2, 9);                // 于2-9放置樱桃, 359cs生效
C(400, {PUFF, SUN}, {1, 2}, 9);      // 于1-9放置小喷, 2-9放置阳光菇
C(359, {LILY, DOOM, COFFEE}, 3, 9);  // 于3-9放置荷叶, 核武, 咖啡豆
复制成功

用卡. 可提供单一坐标, 多行同列, 或多卡同坐标.

代码块
C++
自动换行
复制代码
C(after(110), ...);            // 用法同上, 延迟110cs生效
C(exact(800), ...);            // 不使用炮等效时间, 800cs生效
C(exact(after(..)), ...);      // 以上两者的结合
复制成功

用卡生效时机的变种.

代码块
C++
自动换行
复制代码
C(400, keep(266), ...);        // 生效后266cs铲
C(400, until(1036), ...);      // 1036cs铲
复制成功

指定用卡后的铲除时机.

代码块
C++
自动换行
复制代码
RM(400, SUNFLOWER);            // 于400cs铲除场地上所有小向
RM(400, PUMPKIN, 1, 1);        // 铲除1-1南瓜(没有则不铲)
RM(400, 1, 1);                 // 铲除1-1, 优先铲除非南瓜
RM(400, {1, 2, 5, 6}, 9);      // 铲除1,2,5,6路9列, 优先铲除非南瓜
RM(after(751), ...);           // 用法同上, 延迟751cs生效
复制成功

铲除植物. 可提供单一坐标, 多行同列. 也可指定要铲除的植物种类.

演示功能

代码块
C++
自动换行
复制代码
EnsureExist(GIGA);                           // 确保红眼出现在所有合理行
EnsureExist({GIGA, 2, 3});                   // 确保红眼出现在第2,3行
EnsureExist({{GIGA, 2, 3}, {ZOMBONI, 4}});   // 确保红眼出现在第2,3行, 冰车出现在第4行
复制成功

确保本波某类僵尸出现在某行, 主要用于录制演示视频.

请勿在实际冲关或批量测试中使用.

智能用卡

代码块
C++
自动换行
复制代码
C_IF(exist(ZOMBONI), 400, SPIKEWEED, 1, 9);     // 若本行存在冰车, 于400cs在1-9放置地刺
C_IF(pos({GARG, GIGA}, 680), 400, POT, 1, 8);   // 若本行存在int(x)≤680的白眼或红眼, 于400cs在1-8放置花盆
复制成功

根据本行僵尸情况决定是否用卡. 提供"僵尸是否存在&#​34;, 以及"僵尸x是否小于某值&#​34;两种判断方式. 主要用于实际冲关或批量测试.

录制演示视频时, 更推荐用 EnsureExist, 因为这样可以展现最复杂的情况.


脚本示例

参见此处.​


了解更多

要查看 Simple AvZ 开发幕后的碎碎念, 请参阅 https://gitlab.com/avzlib/AvZLib/-/blob/main/crescendo/SimpleAvZ/doc/About.md.