炉石兄弟 DIY 汇总

2021.3.27 新版智能SIM转移,CardDB_cardIDEnum.cs生成脚本

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# encoding: utf-8
import requests
import re
import json
import os
from tqdm import tqdm

ClassName = {
    "DREAM": "梦境",
    "WHIZBANG": "威兹班"
}

TypeName = {
    "ENCHANTMENT": "附魔",
    "HERO_POWER": "英雄技能"
}

CardSetName = {
    "TB": "TB乱斗模式",
    "HERO_SKINS": "HERO英雄皮肤和技能",
    "THE_BARRENS": "BAR贫瘠之地的锤炼",
    'SCHOLOMANCE': "SCH通灵学园",
    'BASIC': "CORE基本",
    'BATTLEGROUNDS': "BGS酒馆战棋",
    'BOOMSDAY': "BOT砰砰计划",
    'BRM': "BRM黑石山的火焰",
    'BLACK_TEMPLE': "BT外域的灰烬",
    'DEMON_HUNTER_INITIATE': "BT恶魔猎手新兵",
    'YEAR_OF_THE_DRAGON': "DRG巨龙降临",
    "TGT": "AT冠军的试炼",
    'GANGS': "CFM龙争虎斗加基森",
    'CORE': "CORE基本",
    'CREDITS': "CREDITS暴雪制作人员",
    'EXPERT1': "EX经典",
    'HOF': "HOF荣誉室",
    'DALARAN': "DAL暗影崛起",
    'DARKMOON_FAIRE': "DMF疯狂的暗月马戏团",
    'DRAGONS': "DRG巨龙降临",
    'NAXX': "NAX纳克萨玛斯",
    'GILNEAS': "GIL女巫森林",
    'GVG': "GVG地精大战侏儒",
    'ICECROWN': "ICC冰封王座的骑士",
    'UNGORO': "UNG勇闯安戈洛",
    'LOOTAPALOOZA': "LOOT狗头人与地下世界",
    'KARA': "KAR卡拉赞之夜",
    'LOE': "LOE探险者协会",
    'OG': "OG上古之神的低语",
    'MISSIONS': "MISSIONS新手训练",
    'TROLL': "TRL拉斯塔哈的大乱斗",
    'TAVERNS_OF_TIME': "TOT时光酒馆",
    'ULDUM': "ULD奥丹姆奇兵",
    'VANILLA': "VAN经典模式"
}

file_list = requests.get("https://api.hearthstonejson.com/v1/").text
ver_list = re.findall("/v1/(\d+)/all/", file_list)
new_version = max(ver_list)
print(f"new_version: {new_version}")

print("loading global_json...")
global_json = requests.get('https://api.hearthstonejson.com/v1/strings/zhCN/GLOBAL.json').text
global_data = json.loads(global_json)
assert global_data is not None
for name in global_data:
    if "GLOBAL_CLASS_" in name:
        class_name = name.replace('GLOBAL_CLASS_', '')
        if class_name not in ClassName:
            ClassName[class_name] = global_data[name]
    if "GLOBAL_CARD_SET_" in name:
        card_set = name.replace('GLOBAL_CARD_SET_', '')
        if card_set not in CardSetName:
            CardSetName[card_set] = global_data[name]
    elif "GLOBAL_CARDTYPE_" in name:
        card_type = name.replace('GLOBAL_CARDTYPE_', '')
        if card_type not in TypeName:
            TypeName[card_type] = global_data[name]

print("loaded global_json successfully!")

print("loading card_data...")
cardJson_data = ""
cardJson_File = f'{new_version}.json'
if os.path.exists(cardJson_File):
    print("--file mode")
    with open(cardJson_File, "r", encoding='utf-8') as f:
        cardJson_data = f.read()
    assert cardJson_data != ""
else:
    cardJson_url = f'https://api.hearthstonejson.com/v1/{new_version}/all/cards.json'
    print(f"--online mode({cardJson_url})")
    cardJson_req = requests.get(cardJson_url, stream=True)
    cardJson_byte = b''
    pbar = tqdm(total=-1, unit='B', unit_scale=True)
    for chunk in cardJson_req.iter_content(chunk_size=1024):
        assert chunk != None
        cardJson_byte += chunk
        pbar.update(1024)
    pbar.close()
    cardJson_data = cardJson_byte.decode()
    assert cardJson_data != ""
    with open(cardJson_File, "w", encoding='utf-8') as f:
        f.write(cardJson_data)

cardData_temp = json.loads(cardJson_data)
assert cardData_temp is not None
print("loaded card_json successfully!")

cardData = {}
for c in cardData_temp:
    cardData[c['id']] = c

sim_path = os.path.join(os.getcwd(), "cards")
print("loading sim_data from", sim_path)
Sim_Context = []
Sim_CardID = []
Sim_text_idx = {}
Sim_id_idx = {}
for root, dirs, files in os.walk(sim_path):
    for file in files:
        card_id = file.replace('Sim_', '').replace('.cs', '')
        with open(os.path.join(root, file), "r", encoding='utf-8') as sim_file:
            sim_content = sim_file.read()
            if "public" not in sim_content:
                continue
            card_idx = len(Sim_Context)
            Sim_Context.append(sim_content)
            Sim_CardID.append(card_id)
            Sim_id_idx[card_id] = card_idx
            if card_id in cardData and 'text' in cardData[card_id]:
                Sim_text_idx[cardData[card_id]['text']['zhCN']] = card_idx
print("loaded " + str(len(Sim_Context)) + " old_sim_data successfully!")

enum_data = ""
if not os.path.exists('sim'):
    os.mkdir('sim')

print("Creating sim file and CardDB_cardIDEnum.cs")
for cardid, c in cardData.items():
    sim_data = ""
    basic = ""
    if 'type' in c and 'name' in c and c['type'] in TypeName:
        # create enum data
        type = TypeName[c['type']]
        name_cn = c['name']['zhCN']
        name = c['name']['enUS']
        card_set = c['set']
        cardtext_cn = ""
        cardtext = ""
        if 'cardClass' in c and 'cost' in c:
            if type == '法术':
                basic = f"{ClassName[c['cardClass']]} 费用:{c['cost']}"
            if type == '随从' and 'attack' in c and 'health' in c:
                basic = f"{ClassName[c['cardClass']]} 费用:{c['cost']} 攻击力:{c['attack']} 生命值:{c['health']}"
            if type == '武器' and 'attack' in c and 'durability' in c:
                basic = f"{ClassName[c['cardClass']]} 费用:{c['cost']} 攻击力:{c['attack']} 耐久度:{c['durability']}"
            if type == '英雄技能':
                basic = f"{ClassName[c['cardClass']]} 费用:{c['cost']}"
        enum_data += "/// <summary>\n"
        enum_data += f"/// <para>{type} {basic}</para>\n"
        enum_data += f"/// <para>{name}</para>\n"
        enum_data += f"/// <para>{name_cn}</para>\n"
        if 'text' in c:
            cardtext_cn = c['text']['zhCN'].replace('\n', ' ')
            cardtext = c['text']['enUS'].replace('\n', ' ')
            enum_data += f"/// <para>{cardtext}</para>\n"
            enum_data += f"/// <para>{cardtext_cn}</para>\n"
        enum_data += "/// </summary>\n"
        enum_data += f"{cardid} = {c['dbfId']},\n"
        # create sim data
        if type == '附魔':
            continue
        while True:
            if cardid in Sim_id_idx:
                sim_data = Sim_Context[Sim_id_idx[cardid]]
                break
            if 'text' in c and c['text']['zhCN'] in Sim_text_idx:
                idx = Sim_text_idx[c['text']['zhCN']]
                sim_id = Sim_CardID[idx]
                if cardid not in sim_id:
                    sim_data = Sim_Context[idx]
                    # fix
                    sim_data = sim_data.replace(f'class Sim_{sim_id}', f'class Sim_{cardid}')
                    break
            sim_data = "using System;\nusing System.Collections.Generic;\nusing System.Text;\n\n"
            sim_data += "namespace HREngine.Bots\n{\n"
            sim_data += f"\tclass Sim_{cardid} : SimTemplate //* {name_cn} {name}\n\t{{\n"
            if cardtext != "":
                sim_data += "\t\t//" + cardtext + "\n"
                sim_data += "\t\t//" + cardtext_cn + "\n"
            sim_data += "\t\t\n\t\t\n\t}\n}\n"
            break

        if card_set not in CardSetName:
            print("check CardSetName:", cardid)
            continue

        # write sim data
        sim_dir = f"sim\\{CardSetName[card_set]}"
        if not os.path.exists(sim_dir):
            os.mkdir(sim_dir)
        with open(sim_dir + "\\Sim_" + cardid + ".cs", 'w', encoding='utf-8') as sim:
            sim.write(sim_data)
print("Write sim_data to " + os.path.join(os.getcwd(), "sim") + " successfully!")
# write id_enum data
enum_path = os.path.join(os.getcwd(), "CardDB_cardIDEnum.cs")

enum_file_data = '''namespace HREngine.Bots
{
	partial class CardDB
	{
		public enum cardIDEnum
		{
			None,			
'''
enum_data = enum_data.split('\n')
for line in enum_data:
    enum_file_data += f"\t\t\t{line}\n"

enum_file_data += '''        }
	}
}
'''

with open(enum_path, "w", encoding="utf-8") as cardIDEnum:
    cardIDEnum.write(enum_file_data)
print("Write CardDB_cardIDEnum.cs to " + enum_path + " successfully!")
print("all finish")

为炉石兄弟增加祈求机制 3月15日

祈求机制的改法,我们是模仿着兄弟原有的青玉魔像(JadeGolem)的机制来修改,所以我们很多添加函数的位置都选择在JadeGolem旁边,话不多说,这里就直接贴代码了

我们在Hrtprozis.cs文件中 搜索代码public int anzEnemyJadeGolem = 0; 在下面添加两行

1
2
public int OwnInvoke = 0;
public int EnemyInvoke = 0;

搜索代码

1
help.logg("cthunbonus: " + this.anzOgOwnCThunAngrBonus + " " + this.anzOgOwnCThunHpBonus + " " + this.anzOgOwnCThunTaunt);

在下面添加一行

1
help.logg("invoke: " + this.OwnInvoke + " " + this.EnemyInvoke);

搜索代码

1
2
3
4
5
public void updateJadeGolemsInfo(int anzOwnJG, int anzEmemyJG)
{
    anzOwnJadeGolem = anzOwnJG;
    anzEnemyJadeGolem = anzEmemyJG;
}

在下面添加我们的自定义函数updateInvokedInfo

1
2
3
4
5
public void updateInvokedInfo(int Owninvoke, int Ememyinvoke)
{
    this.OwnInvoke = Owninvoke;
    this.EnemyInvoke = Ememyinvoke;
}

然后修改silverfish_HB.cs文件 在

1
Hrtprozis.Instance.updateJadeGolemsInfo(GameState.Get().GetFriendlySidePlayer().GetTag(GAME_TAG.JADE_GOLEM), GameState.Get().GetOpposingSidePlayer().GetTag(GAME_TAG.JADE_GOLEM));

的下一行添加调用我们所写的的自定义函数updateInvokedInfo

1
Hrtprozis.Instance.updateInvokedInfo(GameState.Get().GetFriendlySidePlayer().GetTag((GAME_TAG)1366), GameState.Get().GetOpposingSidePlayer().GetTag((GAME_TAG)1366));

最后修改Playfield.cs文件

搜索代码

1
public int anzEnemyJadeGolem = 0;

在下面添加两行

1
2
public int OwnInvoke = 0;
public int EnemyInvoke = 0;

搜索代码

1
this.anzEnemyJadeGolem = prozis.anzEnemyJadeGolem;

在下面添加两行

1
2
this.OwnInvoke = prozis.OwnInvoke;
this.EnemyInvoke = prozis.EnemyInvoke;

搜索代码

1
this.anzEnemyJadeGolem = p.anzEnemyJadeGolem;

在下面添加两行

1
2
this.OwnInvoke = p.OwnInvoke;
this.EnemyInvoke = p.EnemyInvoke;

搜索代码

1
retval += this.anzOgOwnCThunAngrBonus + this.anzOwnJadeGolem + this.anzOwnElementalsLastTurn;

修改为

1
retval += this.anzOgOwnCThunAngrBonus + this.anzOwnJadeGolem + this.OwnInvoke + this.anzOwnElementalsLastTurn;

搜索函数

1
public CardDB.Card getNextJadeGolem(bool own)

在这个函数上面添加getGalakrondInvoke函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void getGalakrondInvoke(bool own)
{
    int pos = own ? this.ownMinions.Count : this.enemyMinions.Count;
    CardDB.Card DRG_238t14t3kid = CardDB.Instance.getCardDataFromID(CardDB.cardIDEnum.DRG_238t14t3);
    CardDB.Card DRG_238t12t2kid = CardDB.Instance.getCardDataFromID(CardDB.cardIDEnum.DRG_238t12t2);
    if (own) this.OwnInvoke++; else this.EnemyInvoke++;
    TAG_CLASS cls = own ? this.ownHeroStartClass : this.enemyHeroStartClass;
    switch(cls)
    {
        case TAG_CLASS.SHAMAN://萨满
            this.callKid(DRG_238t14t3kid, pos, own);
            break;
        case TAG_CLASS.WARLOCK://术士
            this.callKid(DRG_238t12t2kid, pos, own);
            this.callKid(DRG_238t12t2kid, pos + 1, own);
            break;
        case TAG_CLASS.WARRIOR://战士
            this.minionGetTempBuff(own ? this.ownHero : this.enemyHero, 3, 0);
            break;
        case TAG_CLASS.PRIEST://牧师
            this.drawACard(CardDB.cardIDEnum.None, own, true);
            break;
        case TAG_CLASS.ROGUE://盗贼
            this.drawACard(CardDB.cardIDEnum.DAL_741, own, true);//虚灵跟班
            break;
        default:
            Helpfunctions.Instance.ErrorLog("[提示] 未知的英雄进行祈求");
            break;
    }
    return;
}

还有调试相关修改,如果你不会调试,可以不进行修改 在BoardTester.cs文件

搜索代码

1
int anzEnemyJadeGolem = 0;

在下面添加两行

1
2
int OwnInvoke = 0;
int EnemyInvoke = 0;

搜索

1
if (s.StartsWith("elementals: "))

在上面添加代码

1
2
3
4
5
6
if (s.StartsWith("invoke: "))
{
    String[] ss = s.Split(' ');
    OwnInvoke = Convert.ToInt32(ss[1]);
    EnemyInvoke = Convert.ToInt32(ss[2]);
}

搜索

1
Hrtprozis.Instance.updateJadeGolemsInfo(this.anzOwnJadeGolem, this.anzEnemyJadeGolem);

在下一行添加

1
Hrtprozis.Instance.updateInvokedInfo(this.OwnInvoke, this.EnemyInvoke);

到此修改结束,如果有卡要进行祈求则使用public void getGalakrondInvoke(bool own)函数 如有不懂的地方,或者我写的不明白的地方请评论留言。 具体的文件位置可以看我另一篇文章。

炉石兄弟切换模式功能修复 3月20日

炉石发布会结束之后,兄弟的切换模式功能坏了 调试之后发现,问题主要存在于Option枚举被更新

解决方案 在主程序中替换新版Option(Triton.Game.Mapping.Option) 然后把Triton.Bot.Logic.Bots.DefaultBot.DefaultBot中对应的Option的值也改过去,因为就修改枚举,调用的Option值不会变化。 要修改的两个Option分别是 IN_WILD_MODE = 199 IN_RANKED_PLAY_MODE = 160

两种解决办法 1.直接用DnSpy修改IL代码(注意:直接用DnSpy修改代码会编译不通过) 2.用ILSpy反编译之后将代码复制到DnSpy中(原因好像是DnSpy的内置ILSpy版本比较老)

附:新版Option(2020-03-18)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
public enum Option
{
    INVALID,
    CLIENT_OPTIONS_VERSION,
    SOUND,
    MUSIC,
    CURSOR,
    HUD,
    STREAMING,
    SOUND_VOLUME,
    MUSIC_VOLUME,
    GFX_WIDTH,
    GFX_HEIGHT,
    GFX_FULLSCREEN,
    HAS_SEEN_NEW_CINEMATIC,
    GFX_QUALITY,
    FAKE_PACK_OPENING,
    FAKE_PACK_COUNT,
    HEALTHY_GAMING_DEBUG,
    LAST_SCENE_MODE,
    LOCALE,
    IDLE_KICKER,
    IDLE_KICK_TIME,
    BACKGROUND_SOUND,
    PREFERRED_REGION,
    FORCE_SHOW_IKS,
    PEGUI_DEBUG,
    NEARBY_PLAYERS,
    GFX_WIN_CAMERA_CLEAR,
    GFX_MSAA,
    GFX_FXAA,
    GFX_TARGET_FRAME_RATE,
    GFX_VSYNC,
    CARD_BACK,
    CARD_BACK2,
    LOCAL_TUTORIAL_PROGRESS,
    CONNECT_TO_AURORA,
    SEASON_END_THRESHOLD,
    RECONNECT,
    RECONNECT_TIMEOUT,
    RECONNECT_RETRY_TIME,
    CHANGED_CARDS_DATA,
    KELTHUZADTAUNTS,
    GFX_WIN_POSX,
    GFX_WIN_POSY,
    PREFERRED_CDN_INDEX,
    LAST_FAILED_DOP_VERSION,
    TOUCH_MODE,
    SHOWN_GFX_DEVICE_WARNING,
    INTRO,
    TUTORIAL_LOST_PROGRESS,
    ERROR_SCREEN,
    WELCOME_QUEST_VIEWS,
    IKS_LAST_DOWNLOAD_TIME,
    IKS_LAST_DOWNLOAD_RESPONSE,
    IKS_CACHE_AGE,
    CHEAT_HISTORY,
    PRELOAD_CARD_ASSETS,
    COLLECTION_PREMIUM_TYPE,
    DEV_TIMESCALE,
    IKS_LAST_SHOWN_AD,
    SEEN_PACK_PRODUCT_LIST,
    SHOW_STANDARD_ONLY,
    SHOW_SET_ROTATION_INTRO_VISUALS,
    SKIP_ALL_MULLIGANS,
    IS_TEMPORARY_ACCOUNT_CHEAT,
    TEMPORARY_ACCOUNT_DATA,
    DISALLOWED_CLOUD_STORAGE,
    CREATED_ACCOUNT,
    LAST_HEAL_UP_EVENT_DATE,
    PUSH_NOTIFICATION_STATUS,
    DBF_XML_LOADING,
    HAS_SHOWN_DEVICE_PERFORMANCE_WARNING,
    SCREENSHOT_DIRECTORY,
    SIMULATE_CELLULAR,
    ASSET_DOWNLOAD_ENABLED,
    UPDATE_STATE,
    NATIVE_UPDATE_STATE,
    ASK_UNKNOWN_APPS,
    LAUNCH_COUNT,
    IS_INSTALL_REPORTED,
    FIRST_INSTALL_TIME,
    UPDATED_CLIENT_VERSION,
    UPDATE_STOP_LEVEL,
    MAX_DOWNLOAD_SPEED,
    STREAMING_SPEED_IN_GAME,
    AUTOCONVERT_VIRTUAL_CURRENCY,
    STREAMER_MODE,
    LATEST_SEEN_SHOP_PRODUCT_LIST,
    LATEST_DISPLAYED_SHOP_PRODUCT_LIST,
    RANK_DEBUG,
    DEBUG_CURSOR,
    SERVER_OPTIONS_VERSION,
    PAGE_MOUSE_OVERS,
    COVER_MOUSE_OVERS,
    AI_MODE,
    TIP_PRACTICE_PROGRESS,
    TIP_PLAY_PROGRESS,
    TIP_FORGE_PROGRESS,
    LAST_PRECON_HERO_CHOSEN,
    LAST_CUSTOM_DECK_CHOSEN,
    SELECTED_ADVENTURE,
    SELECTED_ADVENTURE_MODE,
    LAST_SELECTED_STORE_BOOSTER_ID,
    LAST_SELECTED_STORE_ADVENTURE_ID,
    LAST_SELECTED_STORE_HERO_ID,
    LATEST_SEEN_TAVERNBRAWL_SEASON,
    LATEST_SEEN_TAVERNBRAWL_SEASON_CHALKBOARD,
    TIMES_SEEN_TAVERNBRAWL_CRAZY_RULES_QUOTE,
    SET_ROTATION_INTRO_PROGRESS,
    TIMES_MOUSED_OVER_SWITCH_FORMAT_BUTTON,
    RETURNING_PLAYER_BANNER_SEEN,
    LATEST_SEEN_TAVERNBRAWL_SESSION_LIMIT,
    LAST_TAVERN_JOINED,
    LATEST_SEEN_FIRESIDEBRAWL_SEASON,
    LATEST_SEEN_FIRESIDEBRAWL_SEASON_CHALKBOARD,
    LATEST_SEEN_SCHEDULED_DOUBLE_GOLD_VO,
    LATEST_SEEN_SCHEDULED_ALL_POPUPS_SHOWN_VO,
    LATEST_SEEN_SCHEDULED_ENTERED_ARENA_DRAFT,
    LATEST_SEEN_SCHEDULED_LOGIN_FLOW_COMPLETE,
    LATEST_SEEN_WELCOME_QUEST_DIALOG,
    LATEST_SEEN_CURRENCY_CHANGED_VERSION,
    LATEST_SEEN_ADVENTURE,
    LATEST_SEEN_SCHEDULED_WELCOME_QUEST_SHOWN_VO,
    LATEST_SEEN_SCHEDULED_GENERIC_REWARD_SHOWN_VO,
    LATEST_SEEN_SCHEDULED_ARENA_REWARD_SHOWN_VO,
    LAST_SELECTED_STORE_PACK_TYPE,
    WHIZBANG_POPUP_COUNTER,
    SET_ROTATION_INTRO_PROGRESS_NEW_PLAYER,
    HAS_CLICKED_TOURNAMENT,
    HAS_OPENED_BOOSTER,
    HAS_SEEN_TOURNAMENT,
    HAS_SEEN_COLLECTIONMANAGER,
    JUST_FINISHED_TUTORIAL,
    SHOW_ADVANCED_COLLECTIONMANAGER,
    HAS_SEEN_PRACTICE_TRAY,
    HAS_SEEN_HUB,
    HAS_FINISHED_A_DECK,
    HAS_SEEN_FORGE,
    HAS_SEEN_FORGE_HERO_CHOICE,
    HAS_SEEN_FORGE_CARD_CHOICE,
    HAS_SEEN_FORGE_CARD_CHOICE2,
    HAS_SEEN_FORGE_PLAY_MODE,
    HAS_SEEN_FORGE_1WIN,
    HAS_SEEN_FORGE_2LOSS,
    HAS_SEEN_FORGE_RETIRE,
    HAS_SEEN_MULLIGAN,
    HAS_SEEN_EXPERT_AI,
    HAS_SEEN_EXPERT_AI_UNLOCK,
    HAS_SEEN_DECK_HELPER,
    HAS_SEEN_PACK_OPENING,
    HAS_SEEN_PRACTICE_MODE,
    HAS_SEEN_CUSTOM_DECK_PICKER,
    HAS_SEEN_ALL_BASIC_CLASS_CARDS_COMPLETE,
    HAS_BEEN_NUDGED_TO_CM,
    HAS_ADDED_CARDS_TO_DECK,
    TIP_CRAFTING_UNLOCKED,
    HAS_PLAYED_EXPERT_AI,
    HAS_DISENCHANTED,
    HAS_SEEN_SHOW_ALL_CARDS_REMINDER,
    HAS_SEEN_CRAFTING_INSTRUCTION,
    HAS_CRAFTED,
    IN_RANKED_PLAY_MODE,
    HAS_SEEN_THE_COIN,
    HAS_SEEN_100g_REMINDER,
    HAS_SEEN_GOLD_QTY_INSTRUCTION,
    HAS_SEEN_LEVEL_3,
    HAS_LOST_IN_ARENA,
    HAS_RUN_OUT_OF_QUESTS,
    HAS_ACKED_ARENA_REWARDS,
    HAS_SEEN_STEALTH_TAUNTER,
    FRIENDS_LIST_REQUEST_SECTION_HIDE,
    FRIENDS_LIST_CURRENTGAME_SECTION_HIDE,
    FRIENDS_LIST_FRIEND_SECTION_HIDE,
    FRIENDS_LIST_NEARBYPLAYER_SECTION_HIDE,
    FRIENDS_LIST_RECRUIT_SECTION_HIDE,
    HAS_SEEN_HEROIC_WARNING,
    HAS_SEEN_NAXX,
    HAS_ENTERED_NAXX,
    HAS_SEEN_NAXX_CLASS_CHALLENGE,
    BUNDLE_JUST_PURCHASE_IN_HUB,
    HAS_PLAYED_NAXX,
    SPECTATOR_OPEN_JOIN,
    HAS_STARTED_A_DECK,
    HAS_SEEN_COLLECTIONMANAGER_AFTER_PRACTICE,
    HAS_SEEN_BRM,
    HAS_SEEN_LOE,
    HAS_CLICKED_MANA_TAB,
    HAS_SEEN_FORGE_MAX_WIN,
    DEPRECATED_HAS_HEARD_TGT_PACK_VO,
    HAS_SEEN_LOE_STAFF_DISAPPEAR,
    HAS_SEEN_LOE_STAFF_REAPPEAR,
    HAS_SEEN_UNLOCK_ALL_HEROES_TRANSITION,
    SKIP_DECK_TEMPLATE_PAGE_FOR_CLASS_FLAGS,
    HAS_SEEN_DECK_TEMPLATE_SCREEN,
    HAS_CLICKED_DECK_TEMPLATE_REPLACE,
    HAS_SEEN_DECK_TEMPLATE_GHOST_CARD,
    HAS_REMOVED_CARD_FROM_DECK,
    HAS_SEEN_DELETE_DECK_REMINDER,
    HAS_CLICKED_COLLECTION_BUTTON_FOR_NEW_CARD,
    HAS_CLICKED_COLLECTION_BUTTON_FOR_NEW_DECK,
    IN_WILD_MODE,
    HAS_SEEN_WILD_MODE_VO,
    HAS_SEEN_STANDARD_MODE_TUTORIAL,
    NEEDS_TO_MAKE_STANDARD_DECK,
    HAS_SEEN_INVALID_ROTATED_CARD,
    SHOW_SWITCH_TO_WILD_ON_PLAY_SCREEN,
    SHOW_SWITCH_TO_WILD_ON_CREATE_DECK,
    SHOW_WILD_DISCLAIMER_POPUP_ON_CREATE_DECK,
    HAS_SEEN_BASIC_DECK_WARNING,
    GLOW_COLLECTION_BUTTON_AFTER_SET_ROTATION,
    HAS_SEEN_SET_FILTER_TUTORIAL,
    HAS_SEEN_RAF,
    HAS_SEEN_RAF_RECRUIT_URL,
    HAS_SEEN_KARA,
    HAS_SEEN_HEROIC_BRAWL,
    SHOW_INNKEEPER_DECK_DIALOGUE,
    HAS_HEARD_RETURNING_PLAYER_WELCOME_BACK_VO,
    HAS_SEEN_RETURNING_PLAYER_INNKEEPER_CHALLENGE_INTRO,
    DEPRECATED_HAD_ITEM_THAT_WILL_BE_ROTATED_IN_UPCOMING_EVENT,
    SHOULD_AUTO_CHECK_IN_TO_FIRESIDE_GATHERINGS,
    HAS_CLICKED_FIRESIDE_BRAWL_BUTTON,
    HAS_CLICKED_FIRESIDE_GATHERINGS_BUTTON,
    HAS_INITIATED_FIRESIDE_GATHERING_SCAN,
    HAS_DISABLED_GOLDENS_THIS_DRAFT,
    HAS_SEEN_FREE_ARENA_WIN_DIALOG_THIS_DRAFT,
    HAS_SEEN_ICC,
    LATEST_SEEN_ARENA_SEASON_STARTING,
    LATEST_SEEN_ARENA_SEASON_ENDING,
    HAS_SEEN_LOOT,
    HAS_SEEN_LATEST_DUNGEON_RUN_COMPLETE,
    HAS_SEEN_LOOT_CHARACTER_SELECT_VO,
    HAS_SEEN_LOOT_WELCOME_BANNER_VO,
    HAS_SEEN_LOOT_BOSS_FLIP_1_VO,
    HAS_SEEN_LOOT_BOSS_FLIP_2_VO,
    HAS_SEEN_LOOT_BOSS_FLIP_3_VO,
    HAS_SEEN_LOOT_OFFER_TREASURE_1_VO,
    HAS_SEEN_LOOT_OFFER_LOOT_PACKS_1_VO,
    HAS_SEEN_LOOT_OFFER_LOOT_PACKS_2_VO,
    HAS_JUST_SEEN_LOOT_NO_TAKE_CANDLE_VO,
    HAS_SEEN_LOOT_IN_GAME_WIN_VO,
    HAS_SEEN_LOOT_IN_GAME_LOSE_VO,
    HAS_SEEN_LOOT_IN_GAME_MULLIGAN_1_VO,
    HAS_SEEN_LOOT_IN_GAME_MULLIGAN_2_VO,
    HAS_SEEN_LOOT_COMPLETE_ALL_CLASSES_VO,
    HAS_SEEN_LOOT_BOSS_HERO_POWER,
    HAS_SEEN_LOOT_IN_GAME_LOSE_2_VO,
    HAS_SEEN_RANK_REVAMP_END_OF_GAME_WINS_REQUIRED,
    HAS_SEEN_GIL_BONUS_CHALLENGE,
    HAS_SEEN_PLAYED_TESS,
    HAS_SEEN_PLAYED_DARIUS,
    HAS_SEEN_PLAYED_SHAW,
    HAS_SEEN_PLAYED_TOKI,
    HAS_SEEN_BATTLEGROUNDS_BOX_BUTTON
}

深入探究炉石兄弟发现、抉择机制(第一篇:完美修复龙蹄的灾难选择) 3月25日

这是深入探究炉石兄弟发现、抉择机制的第一篇:完美修复龙蹄的灾难选择 先来看看龙蹄这张卡的牌面描述

龙蹄.png

那这个灾难究竟是怎么样的呢?我在网络上找了一张实战的图

龙蹄效果图.png

可以发现这就是类似于抉择一样的选择卡牌之后使用对应的功能,关键代码如下

首先,通过调用makeChoice()函数来确定要执行的动作,然后使用一个switch语句根据返回的值选择相应的操作

  • case 0:是选中间的一张牌
  • case 1:是选最左边的一张牌
  • case 2:是选最右边的一张牌
1
2
3
4
5
6
7
8
await Coroutine.Sleep(555 + makeChoice());
switch (dirtychoice)
{
    case 0: TritonHs.ChooseOneClickMiddle(); break;
    case 1: TritonHs.ChooseOneClickLeft(); break;
    case 2: TritonHs.ChooseOneClickRight(); break;
}
dirtychoice = -1;

再来看看 makeChoice() 相关的代码

首先检查dirtychoice变量的值,根据卡牌的属性(如是否为“发现”或“抉择”卡牌)设置choiceMode。然后,根据当前时间和其他条件,调整dirtychoice的值以决定最终的选择。特别是,通过交换dirtychoice值0和1的逻辑,来调整选择的卡牌顺序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private int makeChoice()
{
    if (dirtychoice < 1)
    {
        ...
        if (sourceCard.GetEntity().HasReferencedTag(GAME_TAG.DISCOVER))
        {
            choiceMode = GAME_TAG.DISCOVER;
            dirtychoice = -1;
        }
        else if (sourceCard.GetEntity().HasReferencedTag(GAME_TAG.ADAPT))
        {
            choiceMode = GAME_TAG.ADAPT;
            dirtychoice = -1;
        }
        DateTime tmp = DateTime.Now;
        ...
        if (dirtychoice == 0) dirtychoice = 1;
        else if (dirtychoice == 1) dirtychoice = 0;
        int ttf = (int)(DateTime.Now - tmp).TotalMilliseconds;
        if (ttf < 3000) return (new Random().Next(ttf < 1300 ? 1300 - ttf : 0, 3100 - ttf));
    }
    else
    {
        Helpfunctions.Instance.logg("选择这张卡牌: " + dirtychoice);
        return (new Random().Next(1100, 3200));
    }
}

再来看看 ChooseOneClickRight() 函数

TritonHs.ChooseOneClickRight()函数中,我们从右侧选择卡牌,这是通过获取所有可选卡牌的列表并选择列表末尾的卡牌来实现的,符合函数名称的直观理解。

1
2
3
4
5
public static void ChooseOneClickRight()
{
    List<Card> friendlyCards = ChoiceCardMgr.Get().GetFriendlyCards();
    Client.LeftClickAt(Client.CardInteractPoint(friendlyCards[friendlyCards.Count - 1]));
}

最后,对于龙蹄这样四张牌的选择,我对原有的选择逻辑进行了调整,以支持不仅仅是三张卡的选择,而是可以根据实际的卡牌数量动态选择。我的思路是直接调用点击卡牌方法,并且我决定去掉"内部函数0转成1,把1转成0,到外部调用的时反常识的顺序(012对应中左右)“这样的操作 于是我直接把内部函数的

1
2
if (dirtychoice == 0) dirtychoice = 1;
else if (dirtychoice == 1) dirtychoice = 0;

直接删掉了,并且学着TritonHs.ChooseOneClickRight()函数来编写外部调用,来替换原来的switch语句

1
2
List<Card> friendlyCards = ChoiceCardMgr.Get().GetFriendlyCards();
Client.LeftClickAt(Client.CardInteractPoint(friendlyCards[dirty]));

原本以为一切顺利,直到我发现兄弟使用了抉择卡牌就会报错。

问题表现为:

  • 内部逻辑显示选择左侧卡牌,兄弟却执行的是选择了右侧卡牌。
  • 内部逻辑显示选择右侧卡牌,兄弟就直接报错了。

经过仔细分析,我发现了问题的根源。问题出在了我们之前未曾注意到的代码分支上,这一分支负责处理抉择卡牌的延迟选择:

1
2
3
4
5
6
7
8
9
if (dirtychoice < 1)
{
    ...
}
else
{
    Helpfunctions.Instance.logg("选择这张卡牌: " + dirtychoice);
    return (new Random().Next(1100, 3200));
}

是的,问题就在于前面我们一直没有注意的第二个分支,他所做的事情是用于抉择卡牌的延迟,当抉择时,dirtychoice 就传入此函数时就已经不为 0,表示已处理,且抉择选项的编号不是从0开始的,而是从1开始,即1代表左侧卡牌,2代表右侧卡牌。因为抉择卡牌只有两个选项,而按照原先的逻辑,返回1时程序错误地尝试选择第二张卡,返回2时则尝试选择第三张卡,从而导致错误。

为解决这个问题,我进行了一番调整。我重写了卡牌选择的逻辑,以确保程序能够正确处理抉择和发现机制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public void ChooseOneClick(int dirty)
{
    if (dirty >= 0)
    {
        switch (dirtychoice)
        {
            case 0: TritonHs.ChooseOneClickMiddle(); break;
            case 1: TritonHs.ChooseOneClickLeft(); break;
            default:
                {
                    List<Card> friendlyCards = ChoiceCardMgr.Get().GetFriendlyCards();
                    if (friendlyCards.Count > dirty)
                        Client.LeftClickAt(Client.CardInteractPoint(friendlyCards[dirty]));
                    else
                        TritonHs.ChooseOneClickRight();//抉择
                    break;
                }
        }
    }
}

通过这种方法,我们能够确保当dirtychoice为0或1时,程序调用原有的函数进行选择;而对于其他值,则进行特殊处理,以区分是通过发现机制还是抉择机制进行的选择。

当然,只是修改了这个之后,还是会发现兄弟还是不会智能的选择卡牌,但是如果强制让其选择第四张牌已经不会报错了。 这个只是我们修复炉石兄弟发现、抉择机制的一个小小的铺垫,实战中还需要进一步的修改。

敬请期待 第二篇:如何更完美的获取发现卡牌的来源(目前兄弟的抉择必须是由兄弟自己打出才能正常的选择,如果你在选牌的界面才打开兄弟则就会提示:没有获得卡牌数据,这对于想要追求完美的我来说不能忍!) 第三篇:如何添加智能选牌数据库和修复类型识别问题

炉石新机制:休眠,流放 4月11日

休眠:双方玩家都无法触及。(感谢吧主) 流放:在手牌中的最左或最右侧使用时触发的额外效果。

最近有点忙,话不多说直接挂代码吧

Minion.cs中还有几处赋值需要新增,可以学习lifesteal进行增加

silverfish_HB.csgetMinions函数加入对dormant数据的读取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
m.dormant = entitiy.GetTag(GAME_TAG.DORMANT);
if (m.dormant > 0)
{
    m.untouchable = true;
    bool flag = false;
    foreach (var en in entitiy.AttachedCards)
    {
        if (en.Id == "BT_737e")
        {
            m.dormant = en.GetTag(GAME_TAG.SCORE_VALUE_1) - en.GetTag(GAME_TAG.SCORE_VALUE_2);
            flag = true;
            break;
        }
    }
    if (!flag) Helpfunctions.Instance.ErrorLog("未能读取到休眠信息");
}

SimTemplate.cs中新增三个函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public virtual void onOutcast(Playfield p, bool own)
{
    return;
}
public virtual void onOutcast(Playfield p, Minion m)
{
    return;
}
public virtual void onDormantEndsTrigger(Playfield p, Minion m)
{
    return;
}

这里解释一下,第一个onOutcast代表法术牌的流放事件 第二个onOutcast代表随从牌的流放事件,类似于战吼 onDormantEndsTrigger指代随从唤醒事件。

Playfield.cs中的

1
public void playACard(Handmanager.Handcard hc, Minion target, int position, int choice, int penality)

函数下搜索

1
c.sim_card.onCardPlay(this, true, target, choice);

在这一行之下添加

1
2
3
4
5
//position从1开始,可以自行测试
if (hc.position == 1 || hc.position == this.owncards.Count)
{
    c.sim_card.onOutcast(this, true);
}

搜索

1
public void placeAmobSomewhere(Handmanager.Handcard hc, int choice, int zonepos)

函数下搜索

1
m.handcard.card.sim_card.getBattlecryEffect(this, m, hc.target, choice);

在这一行之下添加

1
2
3
4
if (hc.position == 1 || hc.position == this.owncards.Count)
{
    m.handcard.card.sim_card.onOutcast(this, m);
}

搜索

1
public void triggerStartTurn(bool ownturn)

函数下搜索

1
m.updateReadyness();

在这一行之下添加

1
2
3
4
5
6
7
8
if (m.dormant > 0 && ownturn == m.own)
{
    m.dormant--;
    if (m.dormant == 0)
    {
        m.handcard.card.sim_card.onDormantEndsTrigger(this, m);
    }
}

至此添加结束,接下来是我写的几张SIM Sim_BT_004.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
namespace HREngine.Bots
{
    class Sim_BT_004 : SimTemplate //* 被禁锢的眼魔 Imprisoned Observer
    {
        //<b>Dormant</b> for 2 turns.When this awakens,deal 2 damage to allenemy minions.
        //<b>休眠</b>两回合。唤醒时,对所有敌方随从造成2点伤害。
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            p.allMinionOfASideGetDamage(!m.own, 2);
        }

    }
}

Sim_BT_009.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace HREngine.Bots
{
    class Sim_BT_009 : SimTemplate //* 被禁锢的阳鳃鱼人 Imprisoned Sungill
    {
        //<b>Dormant</b> for 2 turns.When this awakens, summon two 1/1Murlocs.
        //<b>休眠</b>两回合。唤醒时,召唤两个1/1的鱼人。
        CardDB.Card kid = CardDB.Instance.getCardDataFromID(CardDB.cardIDEnum.BT_009t);
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            p.callKid(kid, m.zonepos, m.own);
            p.callKid(kid, m.zonepos + 1, m.own);
        }

    }
}

Sim_BT_121.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
namespace HREngine.Bots
{
    class Sim_BT_121 : SimTemplate //* 被禁锢的甘尔葛 Imprisoned Gan'arg
    {
        //<b>Dormant</b> for 2 turns.When this awakens,equip a 3/2 Axe.
        //<b>休眠</b>两回合。唤醒时,装备一把3/2的斧子。
        CardDB.Card weapon = CardDB.Instance.getCardDataFromID(CardDB.cardIDEnum.CS2_106);
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            p.equipWeapon(weapon, m.own);
        }

    }
}

Sim_BT_127.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections.Generic;
using System.Text;

namespace HREngine.Bots
{
    class Sim_BT_127 : SimTemplate //* 被禁锢的萨特 Imprisoned Satyr
    {
        //[x]<b>Dormant</b> for 2 turns.When this awakens, reducethe Cost of a random minionin your hand by (5).
        //<b>休眠</b>两回合。唤醒时,使你手牌中的随机一张随从牌的法力值消耗减少(5)点。
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            if (m.own)
            {
                foreach (Handmanager.Handcard hc in p.owncards)
                {
                    hc.manacost = Math.Max(0, hc.manacost - 5);
                }
            }
        }

    }
}

Sim_BT_211.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace HREngine.Bots
{
    class Sim_BT_211 : SimTemplate //* 被禁锢的魔喉 Imprisoned Felmaw
    {
        //[x]<b>Dormant</b> for 2 turns.When this awakens,  attack a random enemy.
        //<b>休眠</b>两回合。唤醒时,随机攻击一个敌人。
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            Minion sm = p.searchRandomMinion(m.own ? p.enemyMinions : p.ownMinions, searchmode.searchHighestAttack);
            if (sm == null) sm = (m.own) ? p.enemyHero : p.ownHero;
            p.minionAttacksMinion(m, sm);
        }

    }
}

Sim_BT_305.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace HREngine.Bots
{
    class Sim_BT_305 : SimTemplate //* 被禁锢的拾荒小鬼 Imprisoned Scrap Imp
    {
        //<b>Dormant</b> for 2 turns.When this awakens,give all minions in your hand +2/+2.
        //<b>休眠</b>两回合。唤醒时,使你手牌中的所有随从牌获得+2/+2。
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            if (m.own)
            {
                foreach (Handmanager.Handcard hc in p.owncards)
                {
                    hc.addattack += 2;
                    hc.addHp += 2;
                }
            }
        }

    }
}

Sim_BT_934.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace HREngine.Bots
{
    class Sim_BT_934 : SimTemplate //* 被禁锢的安塔恩 Imprisoned Antaen
    {
        //[x]<b>Dormant</b> for 2 turns.When this awakens, deal10 damage randomly splitamong all enemies.
        //<b>休眠</b>两回合。唤醒时,造成10点伤害,随机分配到所有敌人身上。
        public override void onDormantEndsTrigger(Playfield p, Minion m)
        {
            p.allCharsOfASideGetRandomDamage(!m.own, 10);
        }
    }
}

2020-05-16 记录对炉石兄弟的修复

今天刚到家,前几天就看到群里面闹得沸沸腾腾,大概是因为炉石使用mono2.0后而且导致了兄弟大面积不可用,本来以为是比较麻烦的问题,结果没想到二十几分钟就解决了,但是由于现在局势紧张,也确实不方便透露具体内容,所以我只能从从技术的角度大致的说一下解决办法

首先是由于更新Mono.dll导致的偏移更新。 mono.dll -> mono-2.0-bdwgc.dll 还有相应的偏移需要更新,但是这里发现有个偏移地址是找不着的,但是经过研究似乎兄弟也没用到这个地址。

只修复上面那个还会有问题,经过调试可以定位到报错的位置,利用CE里面的结构体分析,可以快速的得到新的结构体偏移大小。修改后,测试可用。

最后是自己的一点感想 不希望看到留言邮箱让我发一份文件

  1. 我希望对炉石兄弟更多的是一种技术上的研究而并非是当做一个工具,当然这个只是对于在我博客上学习的人来说的,对于大部分来说那肯定还是一个挂机刷金币的工具,就我自己而言研究炉石兄弟还是给自己带来蛮多的乐趣的,我希望大家也能体会到这种乐趣。
  2. 不要对大神产生依赖性,就像是大家期待的吧主修复,或者说贴吧里哪一位大神来修复,我认为等别人修复使用这肯定不是一个长久之计,除非你愿意掏钱到淘宝上买服务,只要是白嫖,那肯定要做好哪一天不能用的准备。与其等别人修复,还不如尝试着自己动手,我这里给出一个时间数据,我用19年10月左右的那个版本,从头到尾修复可用,差不多花费了三天的时间,这个时间比我想象中的要短了很多,我给出这个数据的目的是为了告诉大家,其实找到一个老版本修复一下并不是什么难事,起码没有想象中的那么难,现在贴吧里,各种博客里也充满了兄弟的资料,现在研究比起早期研究兄弟的人简单太多了,只要你肯花心思花时间去学习。由于一些个人原因,接下来的两三个月里,我对兄弟的研究肯定要停止一段时间了。
  3. 这个博客的不是为了炉石兄弟而开设的,是作为一个个人博客的形象来呈现的,之所以现在的内容都关于炉石兄弟,因为这确实是我前段时间花了很大心思研究的东西,而之后我会分享自己其他方面的编程内容,炉石兄弟作为一个让自己入门C#的工具,其实到这里已经完成了我当时接触它时的使命了。

分享几个自用的用于整理卡牌数据的Python脚本 6月13日

修复换行问题,详情请见评论区

生成卡牌相关文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
import html
import os
import shutil
#输入:C:\\hs\\sim\\ 旧版的Sim文件
#输入:H:\CardDefs.xml 卡牌数据文件

#输出:C:\hs\CardDB_getSimCard.txt 生成的getSimCard文件
#输出:C:\hs\CardDB_cardIDEnum.txt 生成的cardIDEnum文件
#输出:C:\\hs\\newsim\\ 新版的Sim文件,保留旧版的Sim
TAG_CARDTYPE = {
        0: "INVALID",
        1: "GAME",
        2: "PLAYER",
        3: "英雄",
        4: "随从",
        5: "法术",
        6: "附魔",
        7: "武器",
        8: "ITEM",
        9: "TOKEN",
        10: "英雄技能",
        11: "BLANK",
        12: "GAME_MODE_BUTTON",
        22: "MOVE_MINION_HOVER_TARGET"
    }

TAG_CLASS = {
    0: "INVALID",
    1: "巫妖王",
    2: "德鲁伊",
    3: "猎人",
    4: "法师",
    5: "圣骑士",
    6: "潜行者",
    7: "牧师",
    8: "萨满祭司",
    9: "术士",
    10: "战士",
    11: "梦境",
    12: "中立",
    13: "威兹班",
    14: "恶魔猎手"
}
TAG_CARD_SET = {
    0:"INVALID",
    1:"TEST_TEMPORARY",
    2:"0001CORE基本",
    3:"0002EX经典",
    4:"0003HOF荣誉室",
    5:"MISSIONS新手训练",
    6:"DEMO",
    7:"NONE",
    8:"CHEAT",
    9:"BLANK",
    10:"DEBUG_SP",
    11:"PROMO",
    12:"0012NAX纳克萨玛斯",
    13:"0013GVG地精大战侏儒",
    14:"0014BRM黑石山的火焰",
    15:"0015AT冠军的试炼",
    16:"CREDITS暴雪制作人员",
    17:"0017英雄皮肤和技能",
    18:"0018TB乱斗模式",
    19:"SLUSH",
    20:"0020LOE探险者协会",
    21:"0021OG上古之神的低语",
    22:"OG_RESERVE",
    23:"0023KAR卡拉赞之夜",
    24:"KARA_RESERVE",
    25:"0025CFM龙争虎斗加基森",
    26:"GANGS_RESERVE",
    27:"0027UNG勇闯安戈洛",
    1001:"1001ICC冰封王座的骑士",
    1004:"1004LOOT狗头人与地下世界",
    1125:"1125GIL女巫森林",
    1127:"1127BOT砰砰计划",
    1129:"1129TRL拉斯塔哈的大乱斗",
    1130:"1130DAL暗影崛起",
    1158:"1158ULD奥丹姆奇兵" ,
    1347:"1347DRG巨龙降临",
    1439:"1439WE狂野限时回归" ,
    1403:"1403YOD迦拉克隆的觉醒",
    1453:"BATTLEGROUNDS" ,
    1414:"1414BT外域的灰烬" ,
    1463:"1463BT恶魔猎手新兵",
}

fs = open(r"C:\hs\CardDB_getSimCard.txt", 'w', encoding='utf-8')
fb = open(r"C:\hs\CardDB_cardIDEnum.txt", 'w', encoding='utf-8')
cardname = ""
cardnamecn = ""
cardtext = ""
cardtextcn = ""
cardID = ""
enumID = 0
numberID = 0
CARDTYPE = 0
CLASS = 0
CARD_SET = 0
COST = 0
ATK = 0
HEALTH = 0
DURABILITY = 0
with open(r"H:\CardDefs.xml", 'r', encoding='utf-8') as f:
    while 1:
        line = f.readline()
        if not line:
            break
        if r"</Entity>" in line:
            #BATTLEGROUNDS TB
            if CARD_SET != 1453 and CARD_SET != 1143:
                fb.write("/// <summary>\n")
                other = ""
                print(cardID)
                if TAG_CARDTYPE[CARDTYPE] == "法术" :
                    other += " " + TAG_CLASS[CLASS] + " 费用:" + str(COST)
                if TAG_CARDTYPE[CARDTYPE] == "随从" :
                    other += " " + TAG_CLASS[CLASS] + " 费用:" + str(COST) + " 攻击力:" + str(ATK) + " 生命值:" + str(HEALTH)
                if TAG_CARDTYPE[CARDTYPE] == "武器":
                    other += " " + TAG_CLASS[CLASS] + " 费用:" + str(COST) + " 攻击力:" + str(ATK) + " 耐久度:" + str(DURABILITY)
                if TAG_CARDTYPE[CARDTYPE] == "英雄技能":
                    other += " " + TAG_CLASS[CLASS] + " 费用:"
                fb.write("/// <para>"+ TAG_CARDTYPE[CARDTYPE] + other +"</para>\n")
                fb.write("/// <para>"+ cardname +"</para>\n")
                fb.write("/// <para>"+ cardnamecn +"</para>\n")
                fb.write("/// <para>"+ cardtext +"</para>\n")
                fb.write("/// <para>"+ cardtextcn +"</para>\n")
                fb.write("/// </summary>\n")
                fb.write(cardID + " = " + str(numberID) + ",\n")
                if TAG_CARDTYPE[CARDTYPE] != "附魔":
                    fs.write("case cardIDEnum." + cardID + ": return new Sim_" + cardID + "();\n")
                    directory = "C:\\hs\\newsim\\" + TAG_CARD_SET[CARD_SET]
                    if not os.path.exists(directory):
                        os.mkdir(directory)
                    if os.path.exists("C:\\hs\\sim\\Sim_" + cardID + ".cs"):
                        shutil.move("C:\\hs\\sim\\Sim_" + cardID + ".cs", directory + "\\Sim_" + cardID + ".cs")
                    else:
                        with open(directory + "\\Sim_" + cardID + ".cs", 'w', encoding='utf-8') as sim:
                            sim.write("using System;\nusing System.Collections.Generic;\nusing System.Text;\n\n");
                            sim.write("namespace HREngine.Bots\n{\n");
                            sim.write("\tclass Sim_" + cardID +" : SimTemplate //* " + cardnamecn + " " + cardname + "\n\t{\n")
                            sim.write("\t\t//" + cardtext + "\n")
                            sim.write("\t\t//" + cardtextcn + "\n")
                            sim.write("\t\t\n\t\t\n\t}\n}\n")

                #print(cardname, cardnamecn, cardID, TAG_CARDTYPE[CARDTYPE], TAG_CLASS[CLASS], cardtext, cardtextcn)
            cardname = ""
            cardnamecn = ""
            cardtext = ""
            cardtextcn = ""
            cardID = ""
            enumID = 0
            numberID = 0
            CARDTYPE = 0
            CLASS = 0
            CARD_SET = 0
            COST = 0
            ATK = 0
            HEALTH = 0
            DURABILITY = 0

        if "<Entity CardID=\"" in line:
            index1 = line.find("<Entity CardID=\"")
            index2 = line.find("\"", index1 + 16)
            if index1 == -1 or index2 == -1:
                print(line)
                exit(0)
            cardID = line[index1 + 16: index2]
            index1 = line.find("ID=\"", index2)
            index2 = line.find("\"", index1 + 4)
            if index1 == -1 or index2 == -1:
                print(line)
                exit(0)
            numberID = line[index1 + 4: index2]

        if "<Tag enumID=\"" in line:
            enumIDl = line.find("<Tag enumID=\"")
            enumIDr = line.find("\"", enumIDl + 13)
            if enumIDl == -1 or enumIDr == -1:
                print(line)
                exit(0)
            enumID = int(line[enumIDl + 13: enumIDr])
            valuel = line.find("value=\"")
            valuer = line.find("\"", valuel + 7)
            if valuel == -1 or valuer == -1:
                continue
            value = int(line[valuel + 7: valuer])
            #CARDTYPE
            if enumID == 202:
                CARDTYPE = value
            #CLASS
            if enumID == 199:
                CLASS = value
            #CARD_SET
            if enumID == 183:
                CARD_SET = value
            #COST
            if enumID == 48:
                COST = value
            #HEALTH
            if enumID == 45:
                HEALTH = value
            #ATK
            if enumID == 47:
                ATK = value
            #DURABILITY
            if enumID == 187:
                DURABILITY = value
        if "<enUS>" in line:
            if enumID != 185 and enumID != 184:
                continue
            index1 = line.find("<enUS>")
            index2 = line.find("</enUS>", index1 + 6)
            text = ""
            while (index2 == -1):
                text += line[index1 + 6:-1]
                line = f.readline()
                index2 = line.find("</enUS>")
                index1 = -6
            text += line[index1 + 6: index2]

            if index1 == -1 or index2 == -1:
                print("错误:" + line)
                exit(0)
            if enumID == 185:
                cardname = html.unescape(text)
            if enumID == 184:
                cardtext = html.unescape(text)

        if "<zhCN>" in line:
            if enumID != 185 and enumID != 184:
                continue
            index1 = line.find("<zhCN>")
            index2 = line.find("</zhCN>", index1 + 6)

            text = ""
            while (index2 == -1):
                text += line[index1 + 6:-1]
                line = f.readline()
                index2 = line.find("</zhCN>")
                index1 = -6
            text += line[index1 + 6: index2]

            if index1 == -1 or index2 == -1:
                print("错误:" + line)
                exit(0)
            if enumID == 185:
                cardnamecn = html.unescape(text)
            if enumID == 184:
                cardtextcn = html.unescape(text)
[/collapse]

[collapse title="转移PlayRequirement到新的CardDefs文件"]
#输入:H:\CardDefsold.xml
#输出:H:\CardDefs.xml

import html
playreq = {}
playname = {}
with open(r"H:\CardDefsold.xml", 'r', encoding='utf-8') as f:
    for line in f:
        if r"</Entity>" in line:
            if cardID in playreq:
                playname[cardnamecn] = playreq[cardID]
            cardID = ""
            enumID = 0
            cardnamecn = ""
        if "<Entity CardID=\"" in line:
            index1 = line.find("<Entity CardID=\"")
            index2 = line.find("\"", index1 + 16)
            if index1 == -1 or index2 == -1:
                print(line)
                exit(0)
            cardID = line[index1 + 16: index2]

        if "<PlayRequirement" in line:
            if cardID not in playreq:
                playreq[cardID] = ""
            playreq[cardID] += line

        if "<Tag enumID=\"" in line:
            enumIDl = line.find("<Tag enumID=\"")
            enumIDr = line.find("\"", enumIDl + 13)
            if enumIDl == -1 or enumIDr == -1:
                print(line)
                exit(0)
            enumID = int(line[enumIDl + 13: enumIDr])
        if "<zhCN>" in line:
            if enumID != 185 and enumID != 184:
                continue
            index1 = line.find("<zhCN>")
            index2 = line.find("</zhCN>", index1 + 6)
            text = ""
            while (index2 == -1):
                text += line[index1 + 6:-1]
                line = f.readline()
                index2 = line.find("</zhCN>")
                index1 = -6
            text += line[index1 + 6: index2]
            if index1 == -1 or index2 == -1:
                print("错误:" + line)
                exit(0)
            if enumID == 185:
                cardnamecn = html.unescape(text)

#for key in playreq:
    #print(key)
    #print(playreq[key])
with open(r"H:\CardDefscopy.xml", 'w', encoding='utf-8') as fc:
    with open(r"H:\CardDefs.xml", 'r', encoding='utf-8') as f:
        for line in f:
            if r"</Entity>" in line:
                if cardID in playreq:
                    fc.write(playreq[cardID])
                else:
                    if cardnamecn in playname and CARDTYPE != 6 and CARDTYPE != 3:
                        print(cardID, cardnamecn)
                        print(playname[cardnamecn])
                        fc.write(playname[cardnamecn])
                cardID = ""
            if "<Entity CardID=\"" in line:
                index1 = line.find("<Entity CardID=\"")
                index2 = line.find("\"", index1 + 16)
                if index1 == -1 or index2 == -1:
                    print(line)
                    exit(0)
                cardID = line[index1 + 16: index2]
            fc.write(line)
            if "<Tag enumID=\"" in line:
                enumIDl = line.find("<Tag enumID=\"")
                enumIDr = line.find("\"", enumIDl + 13)
                if enumIDl == -1 or enumIDr == -1:
                    print(line)
                    exit(0)
                enumID = int(line[enumIDl + 13: enumIDr])
                valuel = line.find("value=\"")
                valuer = line.find("\"", valuel + 7)
                if valuel == -1 or valuer == -1:
                    continue
                value = int(line[valuel + 7: valuer])
                # CARDTYPE
                if enumID == 202:
                    CARDTYPE = value
            if "<zhCN>" in line:
                if enumID != 185 and enumID != 184:
                    continue
                index1 = line.find("<zhCN>")
                index2 = line.find("</zhCN>", index1 + 6)
                text = ""
                while (index2 == -1):
                    text += line[index1 + 6:-1]
                    line = f.readline()
                    fc.write(line)
                    index2 = line.find("</zhCN>")
                    index1 = -6
                text += line[index1 + 6: index2]
                if index1 == -1 or index2 == -1:
                    print("错误:" + line)
                    exit(0)
                if enumID == 185:
                    cardnamecn = html.unescape(text)
[/collapse]

[collapse title="从指定目录转移有内容的Sim卡到指定位置(和1搭配使用)"]
import os
import shutil

def readFilename(file_dir):
    for root, dirs, files in os.walk(file_dir):
        return files, dirs, root


    
def findstring(pathfile):
    fp = open(pathfile, "r", encoding='UTF-8')  # 注意这里的打开文件编码方式
    strr = fp.read()
    # print strr.find("DoubleVec")
    if (strr.find("public") != -1):
        print('here?')
        return True
    return False


    
def startfind(files, dirs, root):
    for ii in files:
        # print(ii)
        # if ii.endswith('.lua'):
        try:
            if (findstring(root + "\\" + ii)):
                shutil.copy(root + "\\" + ii, "C:\\hs\\sim\\" + ii)
                print(ii)
        except Exception as err:
            print(err)
            continue

    for jj in dirs:
        fi, di, ro = readFilename(root + "\\" + jj)
        startfind(fi, di, ro)


    
if __name__ == '__main__':
    default_dir = u"H:\\Hearthbuddy\\Routines\\DefaultRoutine\\Silverfish\\cards"  # 设置默认打开目录
    file_path = default_dir  # th.expanduser(default_dir)))
    files, dirs, root = readFilename(file_path)
    startfind(files, dirs, root)
[/collapse]

[collapse title="读取卡牌代码,输出策略中的卡牌特征函数"]
import base64
import win32clipboard as w
import win32con

def setText(aString):#写入剪切板
    w.OpenClipboard()
    w.EmptyClipboard()
    w.SetClipboardData(win32con.CF_UNICODETEXT, aString)
    w.CloseClipboard()

def read_varint(data):
    shift = 0
    result = 0
    while True:
        temp = data.pop(0)
        result |= (temp & 0x7f) << shift
        shift += 7
        if (temp & 0x80) == 0:
            break
    return  result

CardDB = {}
with open(r"H:\CardDefs.xml", 'r', encoding='utf-8') as f:
        while True:
            line = f.readline()
            if not line:
                break
            if r"</Entity>" in line:
                CardDB[numberID] = cardID
                cardID = ""
                numberID = 0

            if "<Entity CardID=\"" in line:
                index1 = line.find("<Entity CardID=\"")
                index2 = line.find("\"", index1 + 16)
                if index1 == -1 or index2 == -1:
                    print(line)
                    exit(0)
                cardID = line[index1 + 16: index2]
                index1 = line.find("ID=\"", index2)
                index2 = line.find("\"", index1 + 4)
                if index1 == -1 or index2 == -1:
                    print(line)
                    exit(0)
                numberID = int(line[index1 + 4: index2])

while True:
    try:
        code = input()
        codelist = list(base64.b64decode(code))
    except:
        continue
    if len(codelist) == 0:
        continue
    reserve = read_varint(codelist)
    if reserve != 0:
        continue
    version = read_varint(codelist)
    if version != 1:
        continue
    format = read_varint(codelist)
    heroes = []
    num_heroes = read_varint(codelist)
    for i in range(num_heroes):
        card_id = read_varint(codelist)
        heroes.append(CardDB[card_id])
    cards = []
    num_cards_x1 = read_varint(codelist)
    for i in range(num_cards_x1):
        card_id = read_varint(codelist)
        cards.append([CardDB[card_id], 1])

    num_cards_x2 = read_varint(codelist)
    for i in range(num_cards_x2):
        card_id = read_varint(codelist)
        cards.append([CardDB[card_id], 2])

    num_cards_xn = read_varint(codelist)
    for i in range(num_cards_xn):
        card_id = read_varint(codelist)
        count = read_varint(codelist)
        cards.append({CardDB[card_id], count})
    str = "public List<CardDB.cardIDEnum> ArchetypeCardSet = new List<CardDB.cardIDEnum>\n{\n"
    cardslen = len(cards)
    for i in range(cardslen):
        str += "\tCardDB.cardIDEnum." + cards[i][0]
        if i != cardslen:
            str += ",\n"
    str += "};"
    print(str)
    setText(str)

对于炉石兄弟策略编写的一些感悟 7月30日

1. 关于惩罚

我看到很多策略的编写者,包括我刚入门的时候,喜欢用惩罚来调整兄弟出牌,因为当时我认为兄弟的AI算法就是一个简单的BFS搜索 + 剪枝,只会考虑当前场面最优解。 但现在发现其实兄弟并没有这么蠢,他是考虑了本回合 + 下一回合的操作(只要设置合理),这样前瞻的思路其实完全够用。所以我认为惩罚的作用应该是用于调整大于两回合,兄弟预料不到的前瞻操作。 比如:

  1. 休眠随从的一些特效。
  2. 一些卡牌为打出一些Combo而留牌。
  3. 为了赚取更大场面优势而留牌(比如图腾潮涌,有图腾时直接打出是对场面有益的,但是为了更好的场面所以要通过惩罚留牌)。 那么惩罚该怎么写呢?下面是我的一些感想,希望能给大家提供一定的帮助。 之前看到弄的风风火火的破冰和火焰结界,但现在都没有看到一种合理的解决方法,这是为什么呢? 我思考之后发现,很多人用惩罚去调整,反而效果适得其反。原因就是惩罚像是教小孩子的时候你对他施暴,而策略就是合理的教学,教给他怎么样才是对的。 比如:火焰结界现在流行的版本添加的位置都是错误的,都是和猎人奥秘爆炸陷阱放在同一个地方,这个就属于模拟特效的问题,火焰结界是随从攻击后触发,而爆炸陷阱是攻击前触发,这两个放在一起,问题就出现了,兄弟认为用任意一个随从去攻击效果是一样的,反正都是无法造成伤害,而不是我们一般的思路,用攻击最高的随从去攻击。 还有就是通过惩罚的一些弊端,比如你写惩罚,低血量的时候使用某个有治疗效果的卡牌,返回的是负数的惩罚值。其实返回负数的惩罚值就违背了惩罚的概念。当时这样写的时候只考虑到了低血量的时候使用可以快速的回血,符合我们平常打牌的思维,但是你没有想到的是,兄弟会想办法去构造这种低血量来为了得到这个负数的惩罚(比如火球术打脸),当然这种惩罚如果数额写小一点那还无所谓,因为会被策略中所调整,但是如果数额较大,比如30、50这种程度,那就非常不好了。 所以我推荐惩罚值的编写最好是反常的思路,比如你正常思路中是低血量使用回血牌,而在惩罚值的编写中应当是高血量不使用回血牌,给一个正数的惩罚值,让兄弟知道这样是错误的,我认为这样才是正常的惩罚使用方法。 当然这也还是有一些问题,我还是更加推荐使用策略中血量低于一定的血线则给出一定低的评分更好一些。

2. 关于策略

这里的策略指的是behavior文件夹中的策略文件,这些策略大多用来给场面一个合理的评分。 那么有些兄弟就会说了,我的兄弟打出伏笔操作了,又不能通过惩罚来调整,那该怎么办呢? 我这里有两种方法:

  1. 检查SIM是否编写正确,是否按照真实情况去模拟了
  2. 调整策略中的对战场的评分,比如要加强留牌,那么就给手牌数量的权重调大,如果要加强场面,那就给单个随从的价值调大。 确定SIM的正确性是非常重要的,包括早期兄弟官方写的卡牌也是错误频出,问题就在于没有测试,我认为写一张卡牌首先要保障其语法的正确性,再要保证其模拟的正确性,这才是一张合理正确的卡牌。而出牌速度的问题也是因为模拟错误所导致的,正是因为模拟错误从而导致兄弟认为场面和模拟的不一致,而重新计算。而这个场面模拟是很严格的,就连随从的位置顺序都很重要,比如亡语:召唤一个XXX随从,这样的卡牌的召唤位置一定要是m.zonepos - 1,才是正确的模拟,包括利用p.ownMinion.Count的都是不正确的,甚至有些随从战吼召唤是在随从的一左一右的,这种位置关系也一定要考虑。否则重新计算的时间是非常难熬的。 但是有些随机性的问题是难以模拟的,比如抽一张牌,按一下图腾召唤技能,这样的操作下一步是完全未知的。这种牌是兄弟非常难把控的,原因就在于兄弟只能固定一种随机操作而围绕这种随机的操作的固定结果而计算出的定性结果,这样持续下去兄弟把这一回合的操作都计算完了,结果开始一操作就发现操作结果与模拟的不符合,然后又去重新计算。所以为什么兄弟不适合打弃牌术,我认为大概也是这个原因吧,这个问题来自于兄弟本身的算法架构,也很难修改。 所以,盲目的追求操作速度其实意义没那么大,操作并没有那么长时间,真正长的时间应该是在大场面的计算上,而计算的问题正如我上面所说,应该要尽量写对结果可预料的卡牌编写正常的SIM,而对于具有随机性的卡牌也应该要考虑多种情况分类讨论。最简单的就是对斩杀的判定。

3. 关于_settings.txt

其中的maxwide我想说明一下,这个参数表明的是计算的深度,也就是数值越大,计算的越深入,我认为如果随机性强的卡组,比如弃牌术,应该把这个数字调低一些,因为基于错误的随机模拟上的大量计算是没有意义的。而如果是随机性弱的卡组,比如奥秘法等,这个数字可以适当的调高。这个原因呢,仔细品味我这篇文章,就自然知道了。如果有疑问的可以在评论区提出,欢迎大家指出我的错误,一起讨论学习!

0%