FE水无12相关研究
 
Notifications
Clear all

FE水无12相关研究


OTZzzz
Posts: 86
管理员
Topic starter
(@otzzzz)
Member
Joined: 3 years ago

先贴代码:

#include <avz.h>
#include <dsl/shorthand.h>
#include <filesystem>
#include "custom/lib.h"
#include "mod/mod.h"
#include "DanceCheat/DanceCheat.h"

/* 挂机基础设定 */
constexpr auto INIT_DAT_PATH = "C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\game1_13.dat";
constexpr auto GAME_DAT_PATH = "C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\userdata\\game1_13.dat";
constexpr auto TEMP_DAT_PATH = "C:\\Users\\cresc\\Desktop\\temp\\tmp.dat";
const std::vector<Grid> COB_LIST = {{1, 7}, {2, 7}, {5, 7}, {6, 7}};
const std::vector<Grid> RESTORE_HP_COB_LIST = {{1, 1}, {2, 1}, {5, 1}, {6, 1}};
constexpr AutoplayMode MODE = AutoplayMode::DEMO;
constexpr int FLAG_GOAL = MODE == AutoplayMode::DEMO ? 50 : 4500;

/* 收集数据 */
Logger<Console> logger;
auto start = std::chrono::high_resolution_clock::now();
int sl_count = 0;
int flag_count = 0;
Level level;
Stat total_level_stat("总"), cob_hp_stat("炮HP"), cob_finish_hp_stat("轮末炮HP"), special_fodder_stat("特殊垫材"), fail_cause_stat("SL原因");
Record fodder_record("垫材", 6);
bool stop_skip_tick = false;
bool reload = false;

void pause();
void sl();

const std::vector<PlantType> FODDER_LIST = {PUFF, SUN, SCAREDY, POT, SUNFLOWER, GARLIC};
int fodder_idx = 0;

bool plant_exist(int row, int col) {
    for (auto& p : alivePlantFilter) {
        if (p.Row() + 1 == row && p.Col() + 1 == col) return true;
    }
    return false;
}

std::unordered_set<int> get_giga_threatening_rows() {
    std::unordered_set<int> res;
    for (auto& z : aliveZombieFilter) {
        if (z.Type() == GIGA && z.FreezeCountdown() == 0 && z.State() == 0 && static_cast<int>(z.Abscissa()) <= 671 &&
            !plant_exist(z.Row() + 1, 9)) {
            res.insert(z.Row() + 1);
        }
    }
    return res;
}

std::unordered_set<int> get_col9_empty_rows() {
    std::unordered_set<int> res;
    for (int row : {1, 2, 5, 6}) {
        if (!plant_exist(row, 9))
            res.insert(row);
    }
    return res;
}

void fodder(int row) {
    if (fodder_idx >= FODDER_LIST.size()) {
        logger.Error("试图使用垫材#, 失败.", fodder_idx);
        return;
    }
    if (!IsSeedUsable(FODDER_LIST[fodder_idx])) {
        logger.Error("垫材不可用.");
        pause();
        return;
    }
    ACard(FODDER_LIST[fodder_idx], row, 9);
    fodder_record.add(std::format("[{}, {}]垫{}至{}-9", GetMainObject()->Wave(), NowTime(GetMainObject()->Wave()), fodder_idx, row));
    if (fodder_idx >= 4) {
        special_fodder_stat[std::to_string(fodder_idx)]++;
    }
    fodder_idx++;
}

constexpr int SMART_FODDER_START_TIME = 478;
constexpr int SMART_FODDER_START_TIME_W11 = 553;
constexpr int FIXED_FODDER_TIME = 714;
constexpr int ADDITIONAL_FODDER_END_TIME = 900;
Coroutine place_fodder() {
    fodder_idx = 0;
    int wave = GetMainObject()->Wave();
    int zero_time = NowTime(wave);
    int smart_fodder_start_time = wave == 11 ? SMART_FODDER_START_TIME_W11 : SMART_FODDER_START_TIME;
    co_await [=] { return NowTime(wave) >= zero_time + smart_fodder_start_time; };
    while (NowTime(wave) < zero_time + FIXED_FODDER_TIME) {
        co_await [=] { return !get_giga_threatening_rows().empty() || NowTime(wave) >= zero_time + FIXED_FODDER_TIME; };
        for (int row : get_giga_threatening_rows())
            fodder(row);
    }
    for (int row : get_col9_empty_rows())
        fodder(row);
    co_await [=] { return NowTime(wave) > zero_time + FIXED_FODDER_TIME; };
    while (fodder_idx < FODDER_LIST.size() && NowTime(wave) < zero_time + ADDITIONAL_FODDER_END_TIME) {
        co_await [=] { return !get_col9_empty_rows().empty() || NowTime(wave) >= zero_time + ADDITIONAL_FODDER_END_TIME; };
        for (int row : get_col9_empty_rows())
            fodder(row);
    }
}

void Script()
{
    if (flag_count >= FLAG_GOAL) return;
    EnableModsScoped(DisableItemDrop, RemoveFog);
    logger.SetLevel({LogLevel::DEBUG, LogLevel::ERROR, LogLevel::WARNING});
    SetInternalLogger(logger);
    SetReloadMode(ReloadMode::MAIN_UI_OR_FIGHT_UI);
    SelectCards({ICE, M_ICE, SQUASH, CHERRY, PUFF, SUN, SCAREDY, POT, SUNFLOWER, GARLIC}, MODE == AutoplayMode::DEMO ? 8 : 0);
    DanceCheat(DanceCheatMode::FAST);
    for (auto acc : {2, 3, 5, 6, 8, 10, 12, 13, 15, 16, 18}) SetWavelength(acc, 601);
    for (auto iced : {1, 4, 7, 11, 14, 17}) SetWavelength(iced, 1299);

    auto zombie_type_list = GetZombieTypeList();
    std::vector<int> zombies;
    for (int i = 0; i < 33; i++) {
        if (i == ADR_ZOMBOSS || i == FLAG_ZOMBIE) continue;
        if (zombie_type_list[i] || i == GIGA || i == ZOMBONI) zombies.push_back(i);
    }
    SetZombies(zombies, SetZombieMode::AVERAGE);

    if (MODE != AutoplayMode::SKIPTICK)
        SetGameSpeed(5);
    else
        SkipTick([=] {
            if (stop_skip_tick) return false;
            for (auto p : GetPlantPtrs(COB_LIST, COB)) {
                if (p == nullptr) {
                    int smashing_giga_wave = 999;
                    for (auto& z : aliveZombieFilter) {
                        if (z.Type() == GIGA && z.Abscissa() < 671 && z.State() == 70)
                            smashing_giga_wave = std::min(smashing_giga_wave, z.AtWave() + 1);
                    }
                    if (smashing_giga_wave != GetMainObject()->Wave() - 2) {
                        logger.Error("推测砸炮巨人波数时发生错误.");
                        pause();
                    } else {
                        logger.Warning("w#红砸#-#炮", smashing_giga_wave, p->Row() + 1, p->Col() + 1);
                        fail_cause_stat[std::format("w{}红砸炮", smashing_giga_wave)]++;
                        sl();
                    }
                    return false;
                } else if (p->Hp() < p->HpMax()) {
                    logger.Error("#-#炮损伤", p->Row() + 1, p->Col() + 1);
                    pause();
                    return false;
                }
            }
            if (zombie_type_list[POGO_ZOMBIE]) {
                for (auto& z : aliveZombieFilter) {
                    if (z.Type() == POGO_ZOMBIE && z.Abscissa() < 0) {
                        logger.Error("w#跳进家", z.AtWave() + 1);
                        fail_cause_stat[std::format("w{}跳进家", z.AtWave() + 1)]++;
                        pause();
                        return false;
                    }
                }
            }
            return true;
        });
    Connect('F', [] { SetGameSpeed(5); });
    Connect('G', [] { SetGameSpeed(1); });

    C.SetCards({PUFF, SUN, SCAREDY, POT, SUNFLOWER, GARLIC});
    auto PPCC1 = { At(359) PP(), At(166) C(100,  {1256, 9})};
    auto PPCC2 = { At(288) PP(), At(327) C(196, {1256, 9})};
    auto PPA   = { At(401) PP(), At(208) C(60, {56, 9}), At(402) C(65, {56, 9}), At(275) A(2, 9)};
    auto IPPcc_PP = {
        At(1) I(1, 4),
        At(304) DD(9),
        At(0) Do { CoLaunch(place_fodder); },
        At(918) Shovel({{1, 9}, {2, 9}, {5, 9}, {6, 9}}),
        At(1099) PP()
    };

    OnWave(1_9 % 3, 11_19 % 3) PPCC1 | PPCC2 | IPPcc_PP;
    OnWave(9, 19) { At(601) IPPcc_PP, At(1879) PP() };
    OnWave(10) PPA;

    OnWave(20) {
        At(0) Do {
            for (auto p : GetPlantPtrs(RESTORE_HP_COB_LIST, COB)) {
                if (p->Hp() < 300) {
                    logger.Debug("恢复#-#炮HP #→300", p->Row() + 1, p->Col() + 1, p->Hp());
                    cob_finish_hp_stat[std::to_string(p->Hp())]++;
                    p->Hp() = 300;
                }
            }
            GetMainObject()->Sun() = 8000;
        },
        At(300) Do { kill_all_zombies(); }
    };
}

OnAfterInject({
    if (INIT_DAT_PATH[0] != '\0')
        std::filesystem::copy(INIT_DAT_PATH, GAME_DAT_PATH, std::filesystem::copy_options::overwrite_existing);
    EnterGame();
});

OnBeforeScript({
    if (flag_count > 0 && flag_count % 100 == 0) {
        for (auto p : GetPlantPtrs(COB_LIST, COB))
            cob_hp_stat[std::format("{}-{}", p->Row() + 1, p->Col() + 1)] = p->Hp();
        logger.Debug("#\n#\n#\n#, #", cob_finish_hp_stat,
                                      special_fodder_stat,
                                      fail_cause_stat,
                                      total_level_stat, get_time_estimate(flag_count, FLAG_GOAL, start));
    }
    if (flag_count % 100 == 0) logger.Debug(set_random_seed());
    level = get_level();
    logger.Debug("阳光: #, #, #f, SL次数: # #", GetMainObject()->Sun(), to_string(level), flag_count, sl_count, get_timestamp());
    if (MODE != AutoplayMode::DEMO)
        std::filesystem::copy(GAME_DAT_PATH, TEMP_DAT_PATH + std::to_string(flag_count % 10), std::filesystem::copy_options::overwrite_existing);

    flag_count += 2;
    total_level_stat[to_string(level)]++;
});

void print_fail_stats() {
    logger.Debug(get_prev_wave_stat(3));
    logger.Debug("#", fodder_record);
}

void pause() {
    stop_skip_tick = true;
    SetAdvancedPause(true);
}

void sl() {
    reload = true;
    BackToMain(false);
    EnterGame();
}

OnExitFight({
    if (GetPvzBase()->GameUi() == AAsm::ZOMBIES_WON) {
        Zombie* zombie = nullptr;
        for(auto& z : aliveZombieFilter) {
            if (zombie == nullptr || z.Abscissa() < zombie->Abscissa()) {
                zombie = &z;
            }
        }
        logger.Error("#进家(x=#), 僵尸获胜", ZOMBIE_NAME[zombie->Type()], zombie->Abscissa());
        sl();
    }
    if (GetPvzBase()->GameUi() != AAsm::LEVEL_INTRO && reload) {
        sl_count++;
        std::filesystem::copy(TEMP_DAT_PATH + std::to_string(flag_count % 10), GAME_DAT_PATH, std::filesystem::copy_options::overwrite_existing);
        flag_count = std::clamp(flag_count - 10, 0, INT_MAX);
    }
    reload = false;
});

依赖 https://github.com/Rottenham/pvz-autoplay/tree/main/inc/custom。

2 Replies
OTZzzz
Posts: 86
管理员
Topic starter
(@otzzzz)
Member
Joined: 3 years ago

ND解的几个主要问题,在这里简短说一下。

首先是漏跳跳,这个已基本解决,主要是因为原相位w9对应冰波,但冰+加①+加②并不足以收尾,非得再用第二冰才行,可是这里为了之后冰小偷是用不起第二冰的,故g。解决方法很简单,上f也改为w1冰波起手即可,这样w9对应加②,加②+冰+加①轻松收尾。

其次是巨人砸率。对朴素4垫解法而言,砸率主要来自加①巨人,这部分的确可以用智能垫材(第5、第6垫)解决。然而,事情并不是那么简单。假设冰波原本于524固定垫,现在改为:从478开始观察情况,凡有即将砸炮红眼,则立刻下垫。如此一来,虽然加①巨人没事了,但是对加②巨人的压制力就弱了,导致问题。

此处使用的完整智能垫材逻辑为:

阶段I:冰波478~714,凡有即将砸炮红眼且该路9列未有垫的,立刻下垫

阶段II:冰波714~900,凡有9列无垫的,立刻补垫(若6垫皆已用掉,则不做任何事)

这样一来,在提前下垫的行里,如果导致提前砸扁,那么在714~900期间就可以进行一定程度的补垫。

以上逻辑经测定,500次有红均匀出怪选卡仅砸1次(砸率来自加②出生巨人,其所在行冰波实有一先一后两垫,然而仍旧g了),可以说砸率问题也算是基本解决。

 

哦,那么这个阵有没有什么其它问题呢?

首当其冲的是刷新问题。如你所见,上面的500选卡测试是用均匀出怪而非自然出怪的,原因就是自然出怪实在打不了,尤其冰波热过渡的意外刷新可以说是基本无解,故不考虑自然出怪。我个人反对对一个明显打不了自然出怪的阵强行用自然出怪论证,然后说“你看刷新问题才是最大问题,所以其它问题并不重要”,这种纯属耍流氓逻辑。

不仅如此,即使是均匀出怪,刷新也不是100%稳的,由seml可知均匀出怪有红关中每波意外率为0.33%左右,极端差组合可达20%+,只能说是触目惊心了。

上述测试中是使用的SetWavelength。

 

其次是底线炮损问题。

1ca34fe2a64ecd6f30f7a5f52f913e8c

如图,我是每轮关末都补充底线炮HP的,由以上千f测试结果可见,底线炮那是被啃得七零八落,最差情况有个炮被啃得只剩60HP了,剩下100~200HP的也一大把。由于全难度关不可带炮套件,就基于这一点,就应该可以说在均匀出怪下也是无法无尽了。

此外还有阳光续航问题,但没有以上两个问题严重。

Reply
OTZzzz
Posts: 86
管理员
Topic starter
(@otzzzz)
Member
Joined: 3 years ago

结论?

首先就别惦记自然出怪了,这阵打不了。

均匀出怪呢?打个2f演示没问题,10f冲关其实并不一定稳,理由就是底线炮炮损,连续2次红关把某底线炮HP啃穿并非不可能,这样一来是打不了10f的。

Reply
Scroll to Top
en_USEnglish
Powered by TranslatePress »