From 35b93b765eea2a2b82ad17897dba2bdb4ce18519 Mon Sep 17 00:00:00 2001 From: MistEO Date: Fri, 23 Dec 2022 21:32:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E6=AD=A5=E8=B7=91=E9=80=9A?= =?UTF-8?q?=E6=8A=84=E4=BD=9C=E4=B8=9A=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=88=86=E7=BB=84=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MaaCore/Task/BattleHelper.cpp | 3 +- .../Task/Miscellaneous/BattleProcessTask.cpp | 41 ++- .../Task/Miscellaneous/BattleProcessTask.h | 1 + src/MaaCore/Utils/Algorithm.hpp | 250 ++++++++++++++++++ 4 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 src/MaaCore/Utils/Algorithm.hpp diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index 8b56de727b..4a2f584761 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -142,7 +142,7 @@ bool asst::BattleHelper::update_deployment(bool init) OcrWithPreprocessImageAnalyzer name_analyzer(m_inst_helper.ctrler()->get_image()); name_analyzer.set_task_info("BattleOperName"); name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); - if (name_analyzer.analyze()) { + if (!name_analyzer.analyze()) { Log.error("ocr failed"); continue; } @@ -153,6 +153,7 @@ bool asst::BattleHelper::update_deployment(bool init) m_all_deployment_avatars.insert_or_assign(name, oper.avatar); } pause(); + cancel_oper_selection(); } return true; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 10a3d081b8..155ea2b80a 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -12,14 +12,14 @@ #include "Config/TaskData.h" #include "Controller.h" #include "Task/ProcessTask.h" +#include "Utils/Algorithm.hpp" +#include "Utils/ImageIo.hpp" #include "Utils/Logger.hpp" #include "Vision/MatchImageAnalyzer.h" #include "Vision/Miscellaneous/BattleImageAnalyzer.h" #include "Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h" #include "Vision/OcrWithPreprocessImageAnalyzer.h" -#include "Utils/ImageIo.hpp" - using namespace asst::battle; using namespace asst::battle::copilot; @@ -36,6 +36,7 @@ bool asst::BattleProcessTask::_run() } update_deployment(true); + to_group(); for (size_t i = 0; i < m_combat_data.actions.size() && !need_exit(); ++i) { do_action(i); @@ -56,6 +57,42 @@ bool asst::BattleProcessTask::set_stage_name(const std::string& stage_name) return false; } + + m_combat_data = Copilot.get_data(); + + return true; +} + +bool asst::BattleProcessTask::to_group() +{ + std::unordered_map> groups; + for (const auto& [group_name, oper_list] : m_combat_data.groups) { + std::vector oper_name_list; + ranges::transform(oper_list, std::back_inserter(oper_name_list), [](const auto& oper) { return oper.name; }); + groups.emplace(group_name, std::move(oper_name_list)); + } + + std::unordered_set char_set; + for (const auto& oper : m_cur_deployment_opers | views::values) { + char_set.emplace(oper.name); + } + + auto result_opt = algorithm::get_char_allocation_for_each_group(groups, char_set); + if (!result_opt) { + return false; + } + m_oper_in_group = *result_opt; + + std::unordered_map ungrouped; + const auto& grouped_view = m_oper_in_group | views::values; + for (const auto& name : char_set) { + if (ranges::find(grouped_view, name) != grouped_view.end()) { + continue; + } + ungrouped.emplace(name, name); + } + m_oper_in_group.merge(ungrouped); + return true; } diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index 1de7a09088..6c4420d9dc 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -21,6 +21,7 @@ namespace asst virtual bool _run() override; virtual AbstractTask& this_task() override { return *this; } + bool to_group(); bool do_action(size_t action_index); void notify_action(const battle::copilot::Action& action); bool wait_condition(const battle::copilot::Action& action); diff --git a/src/MaaCore/Utils/Algorithm.hpp b/src/MaaCore/Utils/Algorithm.hpp new file mode 100644 index 0000000000..d6006ea2fe --- /dev/null +++ b/src/MaaCore/Utils/Algorithm.hpp @@ -0,0 +1,250 @@ +#pragma once + +#include +#include +#include +#include + +namespace asst::algorithm +{ + std::optional> get_char_allocation_for_each_group( + const std::unordered_map>& group_list, + const std::unordered_set& char_set) + { + /* + * * dlx 算法简介 + * + * https://oi-wiki.org/search/dlx/ + * + * + * * dlx 算法作用 + * + * 在形如: + * a: 10010 + * b: 01110 + * c: 01001 + * d: 00100 + * e: 11010 + * 这样的数据里, + * dlx 可以找到 {a, c, d} 这样每列恰好出现且仅出现一次 1 的数据, + * 也即对全集的一个精确覆盖: + * a: 10010 + * c: 01001 + * d: 00100 + * 11111 + * + * + * * dlx 算法建模 + * + * dlx 的列分为 [组号] [干员号] 两部分 + * dlx 的行分为 [可能的选择对] [不选择该干员] 两部分 + * + * [可能的选择对]: + * 每行对应一种可能的选择, + * 将组号,干员号对应位置的列设为1 + * + * [不选择该干员]: + * 每行对应不选择某干员的情况, + * 将干员号对应位置的列设为1 + * + * + * * dlx 建模示例 + * + * 有以下分组: + * a: {1, 3, 4} + * b: {2, 3, 5} + * c: {1, 2, 3} + * 拥有的干员: + * {1, 2, 4, 5, 6} + * + * 先处理出所有可能的情况: + * a: {1, 4} + * b: {2, 5} + * c: {1, 2} + * + * 构造表: + * abc 1245 + * 1 100 1000 + * 2 100 0010 + * 3 010 0100 + * 4 010 0001 + * 5 001 1000 + * 6 001 0100 + * 7 000 1000 ~1 + * 9 000 0100 ~2 + * 9 000 0010 ~4 + * A 000 0001 ~5 + * + * 使用dlx求得一组解: + * 一个可能的结果是: + * 行号 {2, 3, 5, A} + * 即 {, , , ~5} + * + * 输出分组结果: + * a: 4 + * b: 2 + * c: 1 + * + */ + + // dlx 算法模板类 + class DancingLinksModel + { + private: + size_t index {}; + std::vector first, size; + std::vector left, right, up, down; + std::vector column, row; + + void remove(const size_t& column_id) + { + left[right[column_id]] = left[column_id]; + right[left[column_id]] = right[column_id]; + for (size_t i = down[column_id]; i != column_id; i = down[i]) { + for (size_t j = right[i]; j != i; j = right[j]) { + up[down[j]] = up[j]; + down[up[j]] = down[j]; + --size[column[j]]; + } + } + } + + void recover(const size_t& column_id) + { + for (size_t i = up[column_id]; i != column_id; i = up[i]) { + for (size_t j = left[i]; j != i; j = left[j]) { + up[down[j]] = down[up[j]] = j; + ++size[column[j]]; + } + } + left[right[column_id]] = right[left[column_id]] = column_id; + } + + public: + size_t answer_stack_size {}; + std::vector answer_stack; + + DancingLinksModel(const size_t& max_node_num, const size_t& max_ans_size) + : first(max_node_num), size(max_node_num), left(max_node_num), right(max_node_num), up(max_node_num), + down(max_node_num), column(max_node_num), row(max_node_num), answer_stack(max_ans_size) + {} + + void build(const size_t& column_id) + { + for (size_t i = 0; i <= column_id; i++) { + left[i] = i - 1; + right[i] = i + 1; + up[i] = down[i] = i; + } + left[0] = column_id; + right[column_id] = 0; + index = column_id; + first.clear(); + size.clear(); + } + + void insert(const size_t& row_id, const size_t& column_id) + { + column[++index] = column_id; + row[index] = row_id; + ++size[column_id]; + down[index] = down[column_id]; + up[down[column_id]] = index; + up[index] = column_id; + down[column_id] = index; + if (!first[row_id]) { + first[row_id] = left[index] = right[index] = index; + } + else { + right[index] = right[first[row_id]]; + left[right[first[row_id]]] = index; + left[index] = first[row_id]; + right[first[row_id]] = index; + } + } + + bool dance(const size_t& depth) + { + if (!right[0]) { + answer_stack_size = depth; + return true; + } + size_t column_id = right[0]; + for (size_t i = right[0]; i != 0; i = right[i]) { + if (size[i] < size[column_id]) { + column_id = i; + } + } + remove(column_id); + for (size_t i = down[column_id]; i != column_id; i = down[i]) { + answer_stack[depth] = row[i]; + for (size_t j = right[i]; j != i; j = right[j]) { + remove(column[j]); + } + if (dance(depth + 1)) { + return true; + } + for (size_t j = left[i]; j != i; j = left[j]) { + recover(column[j]); + } + } + recover(column_id); + return false; + } + }; + + // 建立结点、组、干员与各自 id 的映射关系 + std::vector> node_id_mapping; + std::vector group_id_mapping; + std::vector char_id_mapping; + std::unordered_map group_name_mapping; + std::unordered_map char_name_mapping; + + for (auto& i : group_list) { + group_name_mapping[i.first] = group_id_mapping.size(); + group_id_mapping.emplace_back(i.first); + for (auto& j : i.second) { + if (char_set.contains(j)) { + node_id_mapping.emplace_back(i.first, j); + if (!char_name_mapping.contains(j)) { + char_name_mapping[j] = char_id_mapping.size(); + char_id_mapping.emplace_back(j); + } + } + } + } + + // 建 01 矩阵 + const size_t node_num = node_id_mapping.size(); + const size_t group_num = group_id_mapping.size(); + const size_t char_num = char_id_mapping.size(); + + DancingLinksModel dancing_links_model(2 * node_num + group_num + 2 * char_num + 1, group_num + char_num); + + dancing_links_model.build(group_num + char_num); + + for (size_t i = 0; i < node_num; i++) { + dancing_links_model.insert(i + 1, group_name_mapping[node_id_mapping[i].first] + 1); + dancing_links_model.insert(i + 1, group_num + char_name_mapping[node_id_mapping[i].second] + 1); + } + + for (size_t i = 0; i < char_num; i++) { + dancing_links_model.insert(i + node_num + 1, i + group_num + 1); + } + + // dance!! + bool has_solution = dancing_links_model.dance(0); + + // 判定结果 + if (!has_solution) return std::nullopt; + + std::unordered_map return_value; + + for (size_t i = 0; i < dancing_links_model.answer_stack_size; i++) { + if (dancing_links_model.answer_stack[i] > node_num) break; + return_value.insert(node_id_mapping[dancing_links_model.answer_stack[i] - 1]); + } + + return return_value; + } +} // namespace asst::algorithm