• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

APN-Pucky / tyrant_optimize / 20666674034

02 Jan 2026 09:05PM UTC coverage: 70.382% (+0.02%) from 70.364%
20666674034

Pull #103

github

APN-Pucky
no more need for std-17
Pull Request #103: [WIP] add `_DEBUG_STRAP`

35 of 48 new or added lines in 3 files covered. (72.92%)

2 existing lines in 2 files now uncovered.

4812 of 6837 relevant lines covered (70.38%)

9449982.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.29
/tyrant_optimize.cpp
1
// This program is free software: you can redistribute it and/or modify
2
// it under the terms of the GNU General Public License as published by
3
// the Free Software Foundation, either version 3 of the License, or
4
// (at your option) any later version.
5

6
// This program is distributed in the hope that it will be useful,
7
// but WITHOUT ANY WARRANTY; without even the implied warranty of
8
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9
// GNU General Public License for more details.
10

11
// You should have received a copy of the GNU General Public License
12
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
13
//------------------------------------------------------------------------------
14
// #define NDEBUG
15
#define BOOST_THREAD_USE_LIB
16

17
#ifndef _WIN32
18
#include <unistd.h>
19
#else
20
#include <io.h>
21
#define F_OK 0
22
#define access(f, m) _access((f), (m))
23
#endif
24
#include <array>
25
#include <cassert>
26
#include <chrono>
27
#include <cstring>
28
#include <ctime>
29
#include <functional>
30
#include <iostream>
31
#include <map>
32
#include <unordered_map>
33
#include <set>
34
#include <stack>
35
#include <fstream>
36
#include <string>
37
#include <iomanip>
38
#include <tuple>
39
#include <vector>
40
#include <math.h>
41
#include <boost/algorithm/string.hpp>
42
#include <boost/filesystem.hpp>
43
#include <boost/lexical_cast.hpp>
44
#include <boost/math/distributions/binomial.hpp>
45
#include <boost/optional.hpp>
46
#include <boost/range/join.hpp>
47
#include <boost/thread/barrier.hpp>
48
#include <boost/thread/mutex.hpp>
49
#include <boost/thread/thread.hpp>
50
#include <boost/timer/timer.hpp>
51
#include <boost/tokenizer.hpp>
52
#include "card.h"
53
#include "cards.h"
54
#include "deck.h"
55
#include "read.h"
56
#include "sim.h"
57
#include "tyrant.h"
58
#include "xml.h"
59

60
#include "hPMML.h"
61

62
#define DEFINE_GLOBALS
63
#include "algorithms.h"
64

65
// OpenMP Header
66
#ifdef _OPENMP
67
#include <omp.h>
68
#endif
69

70
// Android Headers
71
#if defined(ANDROID) || defined(__ANDROID__)
72
#include <jni.h>
73
#include <android/log.h>
74
#endif
75

76
using namespace tuo;
77
using namespace proc;
78
// init
79
/*
80
namespace tuo {
81
  unsigned opt_num_threads=4;
82
    gamemode_t gamemode{fight};
83
    OptimizationMode optimization_mode{OptimizationMode::notset};
84
    std::unordered_map<unsigned, unsigned> owned_cards;
85
    const Card* owned_alpha_dominion{nullptr};
86
    bool use_owned_cards{true};
87
    bool opt_skip_unclaimed_decks{false};
88
    //bool opt_trim_unclaimed_decks{false};
89
    unsigned min_deck_len{1};
90
    unsigned max_deck_len{10};
91
    unsigned opt_freezed_cards{0};
92
    unsigned freezed_cards{0};
93
    unsigned fund{0};
94
    long double target_score{100};
95
    long double min_increment_of_score{0};
96
    long double confidence_level{0.99};
97
    bool use_top_level_card{true};
98
    bool use_top_level_commander{true};
99
    bool mode_open_the_deck{false};
100
    bool use_owned_dominions{true};
101
    bool use_maxed_dominions{false};
102
    unsigned use_fused_card_level{0};
103
    unsigned use_fused_commander_level{0};
104
    bool print_upgraded{false};
105
    bool print_values{false};
106
    bool simplify_output{false};
107
    bool show_ci{false};
108
    bool use_harmonic_mean{false};
109
    unsigned iterations_multiplier{10};
110
    unsigned sim_seed{0};
111
    unsigned flexible_iter{20};
112
    unsigned flexible_turn{10};
113
    Requirement requirement;
114
#ifndef NQUEST
115
    Quest quest;
116
#endif
117
    std::unordered_set<unsigned> allowed_candidates;
118
    std::unordered_set<unsigned> disallowed_candidates;
119
    std::chrono::time_point<std::chrono::system_clock> start_time;
120
    long double maximum_time{0};
121
    //anneal
122
    long double temperature = 1000;
123
    long double coolingRate = 0.001;
124
    //genetic
125
    unsigned generations = 50;
126
    unsigned pool_size = 0;
127
    unsigned min_pool_size = 20;
128
    double opt_pool_keep = 1;
129
    double opt_pool_mutate = 1;
130
    double opt_pool_cross = 1;
131
    //fort_climb
132
    unsigned yfpool{0};
133
    unsigned efpool{0};
134
    std::vector<Faction> factions;
135
    bool invert_factions{false};
136
    bool only_recent{false};
137
    bool prefered_recent{false};
138
    unsigned recent_percent{5};
139
    std::vector<Skill::Skill> skills;
140
    bool invert_skills{false};
141
    std::vector<Skill::Skill> prefered_skills;
142
    unsigned prefered_factor{3};
143

144
#if defined(ANDROID) || defined(__ANDROID__)
145
    JNIEnv *envv;
146
    jobject objv;
147
#endif
148
}
149
*/
150

151
static const unsigned int crc32_table[] =
152
    {
153
        0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
154
        0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
155
        0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
156
        0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
157
        0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
158
        0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
159
        0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
160
        0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
161
        0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
162
        0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
163
        0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
164
        0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
165
        0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
166
        0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
167
        0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
168
        0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
169
        0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
170
        0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
171
        0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
172
        0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
173
        0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
174
        0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
175
        0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
176
        0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
177
        0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
178
        0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
179
        0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
180
        0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
181
        0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
182
        0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
183
        0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
184
        0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
185
        0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
186
        0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
187
        0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
188
        0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
189
        0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
190
        0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
191
        0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
192
        0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
193
        0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
194
        0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
195
        0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
196
        0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
197
        0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
198
        0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
199
        0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
200
        0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
201
        0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
202
        0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
203
        0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
204
        0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
205
        0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
206
        0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
207
        0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
208
        0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
209
        0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
210
        0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
211
        0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
212
        0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
213
        0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
214
        0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
215
        0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
216
        0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4};
217

218
unsigned int
219
xcrc32(const unsigned char *buf, int len, unsigned int init)
61,869,917✔
220
{
221
    unsigned int crc = init;
61,869,917✔
222
    while (len--)
1,479,053,663✔
223
    {
224
        crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ *buf) & 255];
1,417,183,746✔
225
        buf++;
1,417,183,746✔
226
    }
227
    return crc;
×
228
}
229

230
unsigned int file_xrcrc32(std::string filename, unsigned int init)
2,913✔
231
{
232
    std::ifstream file;
2,913✔
233
    file.open(filename);
2,913✔
234
    std::string line;
2,913✔
235
    unsigned int crc = init;
2,913✔
236
    while (getline(file, line))
61,875,743✔
237
    {
238
        crc = xcrc32((const unsigned char *)line.c_str(), line.length(), crc);
123,742,747✔
239
    }
240
    return crc;
5,826✔
241
}
2,913✔
242

243
unsigned int checksumcards(std::string prefix)
127✔
244
{
245
    unsigned int crc = 0;
127✔
246
    // loop cardsections
247
    unsigned int ii = 1;
127✔
248

249
    crc = file_xrcrc32(prefix + "data/raids.xml", crc);
127✔
250
    crc = file_xrcrc32(prefix + "data/missions.xml", crc);
127✔
251
    std::string fname = prefix + "data/cards_section_1.xml";
127✔
252
    while (access(fname.c_str(), F_OK) == 0)
2,786✔
253
    {
254
        crc = file_xrcrc32(fname, crc);
5,318✔
255
        ii += 1;
2,659✔
256
        fname = prefix + "data/cards_section_" + std::to_string(ii) + ".xml";
5,318✔
257
    }
258
    return crc;
127✔
259
}
127✔
260

261
void load_ml(std::string prefix)
81✔
262
{
263
    if (use_ml)
81✔
264
    {
265
        win_model = hpmml::Model(prefix + "data/win.pmml");
2✔
266
        stall_model = hpmml::Model(prefix + "data/stall.pmml");
2✔
267
        loss_model = hpmml::Model(prefix + "data/loss.pmml");
2✔
268
        points_model = hpmml::Model(prefix + "data/points.pmml");
2✔
269
    }
270
}
81✔
271

272
// load database map from file
273
void load_db(std::string prefix)
81✔
274
{
275
    if (use_db_load)
81✔
276
    {
277
        _DEBUG_MSG(1, "start loading db\n");
63✔
278
        // open file to read from
279
        std::ifstream file;
63✔
280
        file.open(prefix + "data/database.yml");
63✔
281
        // read map from file
282
        std::string name;
63✔
283
        uint64_t wins, draws, losses, points, count;
63✔
284
        std::string version, check;
63✔
285
        std::string line;
63✔
286
        getline(file, line);
63✔
287
        if (line.find(":") != std::string::npos)
63✔
288
            version = line.substr(line.find(":") + 2);
62✔
289
        std::cout << "DB version " << version << std::endl;
63✔
290
        getline(file, line);
63✔
291
        if (line.find(":") != std::string::npos)
63✔
292
            check = line.substr(line.find(":") + 2);
62✔
293
        std::cout << "DB checksum " << check << std::endl;
63✔
294

295
        if (use_strict_db)
63✔
296
        {
297
            if (version.compare(TYRANT_OPTIMIZER_VERSION) != 0)
63✔
298
            {
299
                std::cout << "DB TUO version outdated" << std::endl;
1✔
300
                std::cout << "DB TUO version " << version << " != " << TYRANT_OPTIMIZER_VERSION << std::endl;
1✔
301
                std::cout << "DB ignoring database.yml" << std::endl;
1✔
302
                use_db_load = false;
1✔
303
                return;
1✔
304
            }
305
            if (check.compare(std::to_string(checksumcards(prefix))) != 0)
124✔
306
            {
307
                std::cout << "DB cards_sections mismatch to db" << std::endl;
×
308
                std::cout << "DB cards_sections checksum " << checksumcards(prefix) << " != " << check << std::endl;
×
309
                std::cout << "DB ignoring database.yml" << std::endl;
×
310
                use_db_load = false;
×
311
                return;
×
312
            }
313
        }
314

315
        std::string hfield;
62✔
316
        std::string hydeck;
62✔
317
        std::string hedeck;
62✔
318
        while (getline(file, line))
53,229✔
319
        {
320
            if (line.rfind("\t\t", 0) == 0 && line.find(":") != std::string::npos)
99,035✔
321
            {
322
                hedeck = line.substr(2, line.find(":") - 2);
45,930✔
323
                std::istringstream reader(line.substr(line.find(":") + 2));
45,930✔
324
                reader >> wins >> draws >> losses >> points >> count;
45,930✔
325
                database[hfield][hydeck][hedeck] = {wins, draws, losses, points, count};
45,930✔
326
                // std::cout << "load db: " << hfield << " " << hydeck << " " << hedeck << " " <<wins<< " "<< draws<< " "<< losses<< " " <<points<< " " <<count << std::endl;
327
            }
45,930✔
328
            else if (line.rfind("\t", 0) == 0 && line.find(":") != std::string::npos)
13,663✔
329
            {
330
                hydeck = line.substr(1, line.find(":") - 1);
6,488✔
331
            }
332
            else if (line.find(":") != std::string::npos)
687✔
333
            {
334
                hfield = line.substr(0, line.find(":"));
687✔
335
            }
336
            else
337
            {
338
                std::cout << "unknown db line: " << line;
53,167✔
339
            }
340
        }
341
        // close file
342
        file.close();
62✔
343
        _DEBUG_MSG(1, "done loading db\n");
62✔
344
    }
63✔
345
}
346

347
// write database map to file
348
void write_db(std::string prefix)
81✔
349
{
350
    if (use_db_write)
81✔
351
    {
352
        _DEBUG_MSG(1, "start writing to db\n");
65✔
353
        // open file to write to
354
        std::ofstream file;
65✔
355
        file.open(prefix + "data/database.yml");
65✔
356
        file << "version: " << TYRANT_OPTIMIZER_VERSION << std::endl;
65✔
357

358
        file << "xml_check_sum: " << checksumcards(prefix) << std::endl;
130✔
359
        auto lines_to_write = db_limit;
65✔
360
        // write map to file
361
        for (auto it1 = database.begin(); lines_to_write != 0 && it1 != database.end(); ++it1)
786✔
362
        {
363
            file << it1->first << ":" << std::endl;
721✔
364
            for (auto it2 = it1->second.begin(); lines_to_write != 0 && it2 != it1->second.end(); ++it2)
7,482✔
365
            {
366
                file << "\t" << it2->first << ":" << std::endl;
6,761✔
367
                for (auto it3 = it2->second.begin(); lines_to_write != 0 && it3 != it2->second.end(); ++it3)
54,617✔
368
                {
369
                    file << "\t\t" << it3->first << ": " << it3->second.wins << " " << it3->second.draws << " " << it3->second.losses << " " << it3->second.points << " " << it3->second.count << std::endl;
47,856✔
370
                    if (lines_to_write > 0)
47,856✔
371
                        lines_to_write--;
×
372
                }
373
            }
374
        }
375
        // close file
376
        file.close();
65✔
377
        _DEBUG_MSG(1, "done writing to db\n");
65✔
378
    }
65✔
379
}
81✔
380

381
void init()
82✔
382
{
383
    debug_str.clear();
82✔
384
    database.clear();
82✔
385
    thread_num_iterations = 0; // written by threads
82✔
386
    thread_results = nullptr;  // written by threads
82✔
387
    thread_best_results = nullptr;
82✔
388
    thread_compare = false;
82✔
389
    thread_compare_stop = false; // written by threads
82✔
390
    destroy_threads;
82✔
391
    opt_num_threads = 4;
82✔
392
    gamemode = fight;
82✔
393
    optimization_mode = OptimizationMode::notset;
82✔
394
    owned_cards.clear();
82✔
395
    owned_alpha_dominion = nullptr;
82✔
396
    use_owned_cards = true;
82✔
397
    opt_skip_unclaimed_decks = false;
82✔
398
    // opt_trim_unclaimed_decks=false;
399
    min_deck_len = 1;
82✔
400
    max_deck_len = 10;
82✔
401
    opt_freezed_cards = 0;
82✔
402
    freezed_cards = 0;
82✔
403
    fund = 0;
82✔
404
    target_score = 100;
82✔
405
    min_increment_of_score = 0;
82✔
406
    confidence_level = 0.99;
82✔
407
    use_top_level_card = true;
82✔
408
    use_top_level_commander = true;
82✔
409
    mode_open_the_deck = false;
82✔
410
    use_owned_dominions = true;
82✔
411
    use_maxed_dominions = false;
82✔
412
    use_fused_card_level = 0;
82✔
413
    use_fused_commander_level = 0;
82✔
414
    print_upgraded = false;
82✔
415
    print_values = false;
82✔
416
    vc_x = 0;
82✔
417
    simplify_output = false;
82✔
418
    show_ci = false;
82✔
419
    use_harmonic_mean = false;
82✔
420
    iterations_multiplier = 10;
82✔
421
    sim_seed = 0;
82✔
422
    flexible_iter = 20;
82✔
423
    flexible_turn = 20;
82✔
424
    eval_iter = 8;
82✔
425
    eval_turn = 8;
82✔
426
    requirement.num_cards.clear();
82✔
427
    for (unsigned i(0); i < Skill::num_skills; ++i)
4,674✔
428
    {
429
        auto s = static_cast<Skill::Skill>(i);
4,592✔
430
        x_skill_scale[s] = 1.0;
4,592✔
431
        n_skill_scale[s] = 1.0;
4,592✔
432
        c_skill_scale[s] = 1.0;
4,592✔
433
    }
434
    hp_scale = 1.0;
82✔
435
    atk_scale = 1.0;
82✔
436
#ifndef NQUEST
437
    // quest = new Quest(); //TODO Quest bugged in Android now here
438
#endif
439
    allowed_candidates.clear();
82✔
440
    disallowed_candidates.clear();
82✔
441
    disallowed_recipes.clear();
82✔
442

443
    // std::chrono::time_point<std::chrono::system_clock> start_time;
444
    maximum_time = 0;
82✔
445
    temperature = 1000;
82✔
446
    coolingRate = 0.001;
82✔
447

448
    generations = 50;
82✔
449
    pool_size = 0;
82✔
450
    min_pool_size = 20;
82✔
451
    opt_pool_keep = 1;
82✔
452
    opt_pool_mutate = 1;
82✔
453
    opt_pool_cross = 1;
82✔
454

455
    min_beam_size = 5;
82✔
456

457
    yfpool = 0;
82✔
458
    efpool = 0;
82✔
459

460
    factions.clear();
82✔
461
    invert_factions = false;
82✔
462
    only_recent = false;
82✔
463
    prefered_recent = false;
82✔
464
    recent_percent = 5;
82✔
465
    skills.clear();
82✔
466
    invert_skills = false;
82✔
467
    prefered_skills.clear();
82✔
468
    prefered_factor = 3;
82✔
469

470
    all_cards.clear();
82✔
471

472
    // fix defaults
473
    for (int i = 0; i < Fix::num_fixes; ++i)
1,148✔
474
        fixes[i] = false;
1,066✔
475

476
    // recommended/default fixes
477
    fixes[Fix::enhance_early] = true;
82✔
478
    fixes[Fix::revenge_on_death] = true;
82✔
479
    fixes[Fix::death_from_bge] = true;
82✔
480
    fixes[Fix::legion_under_mega] = true;
82✔
481
    // Sort of a misnomer, but updated skills are also treated as fixes
482
        fixes[Fix::barrier_each_turn] = true;
82✔
483
        fixes[Fix::dont_evade_mimic_selection] = true;
82✔
484
        fixes[Fix::leech_increase_max_hp] = true;
82✔
485
        fixes[Fix::subdue_before_attack] = true;
82✔
486
        fixes[Fix::counter_without_damage] = true;
82✔
487
        fixes[Fix::poison_after_attacked] = true;
82✔
488

489
        fixes[Fix::corrosive_protect_armor] = true;
82✔
490
    fixes[Fix::reduce_delay_summoned_by_tower] = true;
82✔
491
  
492
    db_limit = -1;
82✔
493
    use_strict_db = true;
82✔
494
    use_db_write = true;
82✔
495
    use_db_load = true;
82✔
496

497
    use_ml = false;
82✔
498
    use_only_ml = false;
82✔
499
    ml_precision = 0.01;
82✔
500
}
82✔
501

502
#if defined(ANDROID) || defined(__ANDROID__)
503
extern "C" JNIEXPORT void
504

505
    JNICALL
506
    Java_de_neuwirthinformatik_alexander_mtuo_TUO_callMain(
507
        JNIEnv *env,
508
        jobject obj /* this */, jobjectArray stringArray)
509
{
510
    envv = env;
511
    objv = obj;
512
    // from: https://stackoverflow.com/questions/8870174/is-stdcout-usable-in-android-ndk and https://gist.github.com/dzhioev/6127982
513
    class androidbuf : public std::streambuf
514
    {
515
    public:
516
        enum
517
        {
518
            bufsize = 512
519
        }; // ... or some other suitable buffer size
520
        androidbuf() { this->setp(buffer, buffer + bufsize - 1); }
521

522
    private:
523
        int overflow(int c)
524
        {
525
            if (c == traits_type::eof())
526
            {
527
                *this->pptr() = traits_type::to_char_type(c);
528
                this->sbumpc();
529
            }
530
            sync_me(c);
531
            return traits_type::not_eof(c);
532
        }
533

534
        void sync_me(int overflow=0) {
535
            if (this->pbase() != this->pptr())
536
            {
537
                char writebuf[bufsize+2];
538
                memcpy(writebuf, this->pbase(), this->pptr() - this->pbase() );
539
                writebuf[this->pptr() - this->pbase() ] = overflow;
540
                writebuf[this->pptr() - this->pbase() + 1 ] = '\0';
541
                //auto sss = std::string(writebuf).c_str();
542
                ///*
543
                __android_log_print(ANDROID_LOG_DEBUG,
544
                                    "TUO_TUO",
545
                                    "%s",
546
                                    writebuf);
547
                //*/
548
                jstring jstr = envv->NewStringUTF(writebuf);
549
                jclass clazz = envv->FindClass("de/neuwirthinformatik/alexander/mtuo/TUO");
550
                jmethodID messageMe = envv->GetMethodID(clazz, "output", "(Ljava/lang/String;)V");
551
                envv->CallVoidMethod(objv, messageMe, jstr);
552
                this->setp(buffer, buffer + bufsize - 1);
553
            }
554
        }
555

556
        int sync()
557
        {
558
            sync_me(0);
559
            return 0;
560
        }
561
        char buffer[bufsize];
562
    };
563
    auto ao = new androidbuf();
564
    auto ae = new androidbuf();
565
    std::cout.rdbuf(ao);
566
    std::cerr.rdbuf(ae);
567
    __android_log_write(ANDROID_LOG_DEBUG, "TUO_TUO", "START");
568
    int stringCount = env->GetArrayLength(stringArray);
569
    char **param = new char *[stringCount];
570
    const char **cparam = new const char *[stringCount];
571
    jstring *strs = new jstring[stringCount];
572
    for (int i = 0; i < stringCount; i++)
573
    {
574
        strs[i] = (jstring)(*env).GetObjectArrayElement(stringArray, i);
575
        cparam[i] = ((*env).GetStringUTFChars(strs[i], NULL));
576
        param[i] = const_cast<char *>(cparam[i]);
577
    }
578

579
    main(stringCount, cparam);
580
    std::cout << std::flush;
581
    __android_log_write(ANDROID_LOG_DEBUG, "TUO_TUO", "END");
582

583
    for (int i = 0; i < stringCount; i++)
584
    {
585
        env->ReleaseStringUTFChars(strs[i], cparam[i]);
586
    }
587
    // std::string text = "return";
588
    // return env->NewStringUTF(text.c_str());
589
    delete[] param;
590
    delete[] cparam;
591
    delete[] strs;
592
    delete ao;
593
    delete ae;
594
}
595

596
extern "C" JNIEXPORT jstring
597

598
    JNICALL
599
    Java_de_neuwirthinformatik_alexander_mtuo_TUO_stringFromJNI(JNIEnv *env,
600
                                                                jobject thiz, jstring s)
601
{
602
    std::string str = env->GetStringUTFChars(s, NULL);
603
    str += "hello.txt";
604
    __android_log_write(ANDROID_LOG_DEBUG, "TUO_TUO", str.c_str());
605
    FILE *file = fopen(str.c_str(), "w+");
606

607
    if (file != NULL)
608
    {
609
        fputs("HELLO WORLD!\n", file);
610
        fflush(file);
611
        fclose(file);
612
    }
613

614
    return env->NewStringUTF("Hello from JNI (with file io)!");
615
}
616
#endif
617
using namespace std::placeholders;
618

619
//------------------------------------------------------------------------------
620

621
//------------------------------------------------------------------------------
622
Deck *find_deck(Decks &decks, const Cards &all_cards, std::string deck_name)
618✔
623
{
624
    Deck *deck = decks.find_deck_by_name(deck_name);
618✔
625
    if (deck != nullptr)
618✔
626
    {
627
        deck->resolve();
562✔
628
        return (deck);
562✔
629
    }
630
    decks.decks.emplace_back(Deck{all_cards});
112✔
631
    deck = &decks.decks.back();
56✔
632
    deck->set(deck_name);
56✔
633
    deck->resolve();
56✔
634
    return (deck);
56✔
635
}
636
//---------------------- $80 deck optimization ---------------------------------
637

638
unsigned get_required_cards_before_upgrade(std::unordered_map<unsigned, unsigned> &owned_cards,
98,490✔
639
                                           const std::vector<const Card *> &card_list, std::map<const Card *, unsigned> &num_cards)
640
{
641
    unsigned deck_cost = 0;
98,490✔
642
    std::set<const Card *> unresolved_cards;
98,490✔
643
    for (const Card *card : card_list)
444,264✔
644
    {
645
        ++num_cards[card];
345,774✔
646
        unresolved_cards.insert(card);
345,774✔
647
    }
648
    // un-upgrade according to type/category
649
    // * use fund for normal cards
650
    // * use only top-level cards for initial (basic) dominion (Alpha Dominion) and dominion material (Dominion Shard)
651
    while (!unresolved_cards.empty())
438,043✔
652
    {
653
        // pop next unresolved card
654
        auto card_it = unresolved_cards.end();
339,553✔
655
        auto card = *(--card_it);
339,553✔
656
        unresolved_cards.erase(card_it);
339,553✔
657

658
        // assume unlimited common/rare level-1 cards (standard set)
659
        if ((card->m_set == 1000) && (card->m_rarity <= 2) && (card->is_low_level_card()))
339,553✔
660
        {
661
            continue;
×
662
        }
663

664
        // keep un-defused (top-level) basic dominion & its material
665
        if ((card->m_id == 50002) || (card->m_category == CardCategory::dominion_material))
339,553✔
666
        {
667
            continue;
×
668
        }
669

670
        // defuse if inventory lacks required cards and recipe is not empty
671
        if ((fund || (card->m_category != CardCategory::normal)) && (owned_cards[card->m_id] < num_cards[card]) && !card->m_recipe_cards.empty())
339,553✔
672
        {
673
            unsigned num_under = num_cards[card] - owned_cards[card->m_id];
×
674
            num_cards[card] = owned_cards[card->m_id];
×
675

676
            // do count cost (in SP) only for normal cards
677
            if (card->m_category == CardCategory::normal)
×
678
            {
679
                deck_cost += num_under * card->m_recipe_cost;
×
680
            }
681

682
            // enqueue recipe cards as unresolved
683
            for (auto recipe_it : card->m_recipe_cards)
×
684
            {
685
                num_cards[recipe_it.first] += num_under * recipe_it.second;
×
686
                unresolved_cards.insert(recipe_it.first);
×
687
            }
688
        }
689
    }
690
    return deck_cost;
98,490✔
691
}
98,490✔
692

693
inline unsigned get_required_cards_before_upgrade(const std::vector<const Card *> &card_list, std::map<const Card *, unsigned> &num_cards)
98,490✔
694
{
695
    return get_required_cards_before_upgrade(owned_cards, card_list, num_cards);
30,214✔
696
}
697

698
unsigned get_deck_cost(const Deck *deck)
33,563✔
699
{
700
    if (!use_owned_cards)
33,563✔
701
    {
702
        return 0;
703
    }
704
    std::map<const Card *, unsigned> num_in_deck;
33,563✔
705
    unsigned deck_cost = 0;
33,563✔
706
    if (deck->commander)
33,563✔
707
    {
708
        deck_cost += get_required_cards_before_upgrade({deck->commander}, num_in_deck);
60,428✔
709
    }
710
    deck_cost += get_required_cards_before_upgrade(deck->cards, num_in_deck);
33,563✔
711
    for (auto it : num_in_deck)
224,553✔
712
    {
713
        unsigned card_id = it.first->m_id;
192,289✔
714
        if (it.second > owned_cards[card_id])
192,289✔
715
        {
716
            return UINT_MAX;
1,299✔
717
        }
718
    }
719
    return deck_cost;
720
}
33,563✔
721

722
bool is_in_recipe(const Card *card, const Card *material)
×
723
{
724
    // is it already material?
725
    if (card == material)
×
726
    {
727
        return true;
728
    }
729

730
    // no recipes
731
    if (card->m_recipe_cards.empty())
×
732
    {
733
        return false;
734
    }
735

736
    // avoid illegal
737
    if (card->m_category == CardCategory::dominion_material)
×
738
    {
739
        return false;
740
    }
741

742
    // check recursively
743
    for (auto recipe_it : card->m_recipe_cards)
×
744
    {
745
        // is material found?
746
        if (recipe_it.first == material)
×
747
        {
748
            return true;
×
749
        }
750

751
        // go deeper ...
752
        if (is_in_recipe(recipe_it.first, material))
×
753
        {
754
            return true;
755
        }
756
    }
757

758
    // found nothing
759
    return false;
760
}
761

762
bool is_owned_or_can_be_fused(const Card *card)
35,315✔
763
{
764
    if (owned_cards[card->m_id])
35,315✔
765
    {
766
        return true;
767
    }
768
    if (!fund && (card->m_category == CardCategory::normal))
35,155✔
769
    {
770
        return false;
771
    }
772
    std::map<const Card *, unsigned> num_in_deck;
×
773
    unsigned deck_cost = get_required_cards_before_upgrade({card}, num_in_deck);
×
774
    if ((card->m_category == CardCategory::normal) && (deck_cost > fund))
×
775
    {
776
        while (!card->is_low_level_card() && (deck_cost > fund))
×
777
        {
778
            card = card->downgraded();
×
779
            num_in_deck.clear();
×
780
            deck_cost = get_required_cards_before_upgrade({card}, num_in_deck);
×
781
        }
782
        if (deck_cost > fund)
×
783
        {
784
            return false;
785
        }
786
    }
787
    std::map<unsigned, unsigned> num_under;
×
788
    for (auto it : num_in_deck)
×
789
    {
790
        if (it.second > owned_cards[it.first->m_id])
×
791
        {
792
            if ((card->m_category == CardCategory::dominion_alpha) && use_maxed_dominions && !is_in_recipe(card, owned_alpha_dominion))
×
793
            {
794
                if (it.first->m_id != 50002)
×
795
                {
796
                    num_under[it.first->m_id] += it.second - owned_cards[it.first->m_id];
×
797
                }
798
                continue;
×
799
            }
800
            return false;
×
801
        }
802
    }
803
    if (!num_under.empty())
×
804
    {
805
        std::map<const Card *, unsigned> &refund = dominion_refund[owned_alpha_dominion->m_fusion_level][owned_alpha_dominion->m_level];
×
806
        for (auto &refund_it : refund)
×
807
        {
808
            unsigned refund_id = refund_it.first->m_id;
×
809
            if (!num_under.count(refund_id))
×
810
            {
811
                continue;
×
812
            }
813
            num_under[refund_id] = safe_minus(num_under[refund_id], refund_it.second);
×
814
            if (!num_under[refund_id])
×
815
            {
816
                num_under.erase(refund_id);
×
817
            }
818
        }
819
    }
820
    return num_under.empty();
×
821
}
×
822

823
std::string alpha_dominion_cost(const Card *dom_card)
×
824
{
825
    assert(dom_card->m_category == CardCategory::dominion_alpha);
×
826
    if (!owned_alpha_dominion)
×
827
    {
828
        return "(no owned alpha dominion)";
×
829
    }
830
    std::unordered_map<unsigned, unsigned> _owned_cards;
×
831
    std::unordered_map<unsigned, unsigned> refund_owned_cards;
×
832
    std::map<const Card *, unsigned> num_cards;
×
833
    std::map<const Card *, unsigned> &refund = dominion_refund[owned_alpha_dominion->m_fusion_level][owned_alpha_dominion->m_level];
×
834
    unsigned own_dom_id = 50002;
×
835
    if (is_in_recipe(dom_card, owned_alpha_dominion))
×
836
    {
837
        own_dom_id = owned_alpha_dominion->m_id;
×
838
    }
839
    else if (owned_alpha_dominion->m_id != 50002)
×
840
    {
841
        for (auto &it : refund)
×
842
        {
843
            if (it.first->m_category != CardCategory::dominion_material)
×
844
            {
845
                continue;
×
846
            }
847
            refund_owned_cards[it.first->m_id] += it.second;
×
848
        }
849
    }
850
    _owned_cards[own_dom_id] = 1;
×
851
    get_required_cards_before_upgrade(_owned_cards, {dom_card}, num_cards);
×
852
    std::string value("");
×
853
    for (auto &it : num_cards)
×
854
    {
855
        if (it.first->m_category != CardCategory::dominion_material)
×
856
        {
857
            continue;
×
858
        }
859
        value += it.first->m_name + " x " + std::to_string(it.second) + ", ";
×
860
    }
861
    if (!is_in_recipe(dom_card, owned_alpha_dominion))
×
862
    {
863
        num_cards.clear();
×
864
        get_required_cards_before_upgrade(_owned_cards, {owned_alpha_dominion}, num_cards);
×
865
        value += "using refund: ";
×
866
        for (auto &it : refund)
×
867
        {
868
            signed num_under(it.second - (signed)num_cards[it.first]);
×
869
            value += it.first->m_name + " x " + std::to_string(it.second) + "/" + std::to_string(num_under) + ", ";
×
870
        }
871
    }
872
    // remove trailing ', ' for non-empty string / replace empty by '(none)'
873
    if (!value.empty())
×
874
    {
875
        value.erase(value.end() - 2, value.end());
×
876
    }
877
    else
878
    {
879
        value += "(none)";
×
880
    }
881
    return value;
×
882
}
×
883

884
// check if claim_cards is necessary => i.e. can the deck be build from the ownedcards
885
bool claim_cards_needed(const std::vector<const Card *> &card_list)
34,703✔
886
{
887
    std::map<const Card *, unsigned> num_cards;
34,703✔
888
    get_required_cards_before_upgrade(card_list, num_cards);
34,703✔
889
    for (const auto &it : num_cards)
158,543✔
890
    {
891
        const Card *card = it.first;
127,194✔
892
        if (card->m_category == CardCategory::dominion_material && use_maxed_dominions)
127,194✔
893
            continue;
×
894
        if (card->m_category == CardCategory::dominion_alpha && use_maxed_dominions)
127,194✔
895
            continue;
×
896
        unsigned num_to_claim = safe_minus(it.second, owned_cards[card->m_id]);
127,194✔
897
        if (num_to_claim > 0)
898
        {
899
            return true;
34,703✔
900
        }
901
    }
902
    return false;
903
}
34,703✔
904

905
void claim_cards(const std::vector<const Card *> &card_list)
10✔
906
{
907
    std::map<const Card *, unsigned> num_cards;
10✔
908
    get_required_cards_before_upgrade(card_list, num_cards);
10✔
909
    for (const auto &it : num_cards)
65✔
910
    {
911
        const Card *card = it.first;
55✔
912
        if (card->m_category == CardCategory::dominion_material)
55✔
913
            continue;
×
914
        if (card->m_category == CardCategory::dominion_alpha)
55✔
915
            continue;
×
916
        unsigned num_to_claim = safe_minus(it.second, owned_cards[card->m_id]);
110✔
917
        if (num_to_claim > 0)
55✔
918
        {
919
            owned_cards[card->m_id] += num_to_claim;
55✔
920
            if (debug_print >= 0)
55✔
921
            {
922
                std::cerr << "WARNING: Need extra " << num_to_claim << " " << card->m_name << " to build your initial deck: adding to owned card list.\n";
55✔
923
            }
924
        }
925
    }
926
}
10✔
927

928
bool valid_deck(Deck *your_deck)
12,687✔
929
{
930
    if (claim_cards_needed({your_deck->commander}))
12,687✔
931
        return false;
932
    if (claim_cards_needed(your_deck->cards))
12,687✔
933
        return false;
934
    if (your_deck->alpha_dominion && claim_cards_needed({your_deck->alpha_dominion}))
9,333✔
935
        return false;
936
    return true; // valid
937
}
938
//------------------------------------------------------------------------------
939
FinalResults<long double> compute_score(const EvaluatedResults &results, std::vector<long double> &factors)
3,994✔
940
{
941
    FinalResults<long double> final{0, 0, 0, 0, 0, 0, results.second};
3,994✔
942
    long double max_possible = max_possible_score[(size_t)optimization_mode];
3,994✔
943
    for (unsigned index(0); index < results.first.size(); ++index)
11,940✔
944
    {
945
        // std::cout << results.second << " " << results.first[index].points << " " << results.first[index].count << std::endl;
946
        final.wins += results.first[index].wins * factors[index] * results.second / results.first[index].count;
7,946✔
947
        final.draws += results.first[index].draws * factors[index] * results.second / results.first[index].count;
7,946✔
948
        final.losses += results.first[index].losses * factors[index] * results.second / results.first[index].count;
7,946✔
949
        // APN
950
        auto trials = results.second;
7,946✔
951
        auto prob = 1 - confidence_level;
7,946✔
952
        auto successes = results.first[index].points * results.second / results.first[index].count / max_possible;
7,946✔
953
        if (successes > trials)
7,946✔
954
        {
955
            successes = trials;
×
956
            // printf("WARNING: biominal successes > trials");
957
            _DEBUG_MSG(2, "WARNING: biominal successes > trials");
×
958
        }
959

960
        auto lower_bound = boost::math::binomial_distribution<>::find_lower_bound_on_p(trials, successes, prob) * max_possible;
7,946✔
961
        auto upper_bound = boost::math::binomial_distribution<>::find_upper_bound_on_p(trials, successes, prob) * max_possible;
7,946✔
962
        if (use_harmonic_mean)
7,946✔
963
        {
964
            final.points += factors[index] / (results.first[index].points * results.second / results.first[index].count);
×
965
            final.points_lower_bound += factors[index] / lower_bound;
×
966
            final.points_upper_bound += factors[index] / upper_bound;
×
967
        }
968
        else
969
        {
970
            final.points += results.first[index].points * factors[index] * results.second / results.first[index].count;
7,946✔
971
            final.points_lower_bound += lower_bound * factors[index];
7,946✔
972
            final.points_upper_bound += upper_bound * factors[index];
7,946✔
973
        }
974
    }
975
    long double factor_sum = std::accumulate(factors.begin(), factors.end(), 0.);
3,994✔
976
    final.wins /= factor_sum * (long double)results.second;
3,994✔
977
    final.draws /= factor_sum * (long double)results.second;
3,994✔
978
    final.losses /= factor_sum * (long double)results.second;
3,994✔
979
    if (use_harmonic_mean)
3,994✔
980
    {
981
        final.points = factor_sum / ((long double)results.second * final.points);
×
982
        final.points_lower_bound = factor_sum / final.points_lower_bound;
×
983
        final.points_upper_bound = factor_sum / final.points_upper_bound;
×
984
    }
985
    else
986
    {
987
        final.points /= factor_sum * (long double)results.second;
3,994✔
988
        final.points_lower_bound /= factor_sum;
3,994✔
989
        final.points_upper_bound /= factor_sum;
3,994✔
990
    }
991
    return final;
3,994✔
992
}
993
//------------------------------------------------------------------------------
994
//------------------------------------------------------------------------------
995
// Per thread data.
996
// seed should be unique for each thread.
997
// d1 and d2 are intended to point to read-only process-wide data.
998

999
void SimulationData::set_decks(std::vector<Deck *> const your_decks_, std::vector<Deck *> const &enemy_decks_)
3,506✔
1000
{
1001
    for (unsigned i(0); i < your_decks_.size(); ++i)
7,453✔
1002
    {
1003
        your_decks[i].reset(your_decks_[i]->clone());
3,947✔
1004
        your_hands[i]->deck = your_decks[i].get();
3,947✔
1005
    }
1006
    for (unsigned i(0); i < enemy_decks_.size(); ++i)
7,453✔
1007
    {
1008
        enemy_decks[i].reset(enemy_decks_[i]->clone());
3,947✔
1009
        enemy_hands[i]->deck = enemy_decks[i].get();
3,947✔
1010
    }
1011
}
3,506✔
1012

1013
std::vector<std::unordered_map<std::string, std::string>> Process::samples()
1✔
1014
{
1015
    std::vector<std::unordered_map<std::string, std::string>> samples;
1✔
1016

1017
    int i = 0;
1✔
1018
    // loop over partial ids and prefix with f add to fmap
1019
    auto ids = partial_ids();
1✔
1020
    for (auto const &ydeck : your_decks)
2✔
1021
    {
1022
        auto yids = ydeck->sorted_ids();
1✔
1023
        for (auto const &edeck : enemy_decks)
2✔
1024
        {
1025
            auto eids = edeck->sorted_ids();
1✔
1026
            std::unordered_map<std::string, std::string> fmap;
1✔
1027
            i = 0;
1✔
1028
            for (auto const &id : ids)
92✔
1029
            {
1030
                fmap.emplace("f" + std::to_string(i), std::to_string(id));
182✔
1031
                i++;
91✔
1032
            }
1033
            i = 0;
1✔
1034
            for (auto const &id : yids)
13✔
1035
            {
1036
                fmap.emplace("y" + std::to_string(i), std::to_string(id->m_id));
24✔
1037
                i++;
12✔
1038
            }
1039
            // fill up with zeros up to id 12
1040
            while (i < 12)
1✔
1041
            {
1042
                fmap.emplace("y" + std::to_string(i), std::to_string(0));
×
1043
                i++;
×
1044
            }
1045
            i = 0;
1✔
1046
            for (auto const &id : eids)
13✔
1047
            {
1048
                fmap.emplace("e" + std::to_string(i), std::to_string(id->m_id));
24✔
1049
                i++;
12✔
1050
            }
1051
            // fill up with zeros up to id 12
1052
            while (i < 12)
1✔
1053
            {
1054
                fmap.emplace("e" + std::to_string(i), std::to_string(0));
×
1055
                i++;
×
1056
            }
1057
            samples.emplace_back(fmap);
1✔
1058
        }
1✔
1059
    }
1✔
1060
    /*
1061
    auto myMap = samples.at(0);
1062
    for(auto it = myMap.begin(); it != myMap.end(); ++it)
1063
    {
1064
        std::cout << it->first << " " << it->second  <<"\n";
1065
    }
1066
    */
1067

1068
    return samples;
1✔
1069
}
1✔
1070

1071
std::vector<std::array<std::string, 3>> Process::hashes()
3,430✔
1072
{
1073
    std::vector<std::array<std::string, 3>> hashes;
3,430✔
1074
    hashes.reserve(your_decks.size() * enemy_decks.size());
3,430✔
1075
    std::string hash = partial_hash();
3,430✔
1076
    for (auto const &ydeck : your_decks)
7,088✔
1077
    {
1078
        for (auto const &edeck : enemy_decks)
9,064✔
1079
        {
1080
            hashes.emplace_back(std::array<std::string, 3>{hash, ydeck->hash(), edeck->hash()});
16,218✔
1081
        }
1082
    }
1083
    return hashes;
3,430✔
1084
}
3,430✔
1085

1086
std::vector<unsigned int> Process::partial_ids()
3,431✔
1087
{
1088
    std::vector<unsigned int> ids;
3,431✔
1089
    ids.emplace_back(gamemode);
3,431✔
1090
    ids.emplace_back(optimization_mode);
3,431✔
1091
    for (int i = 0; i < Fix::num_fixes; ++i)
48,034✔
1092
    {
1093
        ids.emplace_back(fixes[i]);
44,603✔
1094
    }
1095
    ids.emplace_back(flexible_iter);
3,431✔
1096
    ids.emplace_back(flexible_turn);
3,431✔
1097
    ids.emplace_back(eval_iter);
3,431✔
1098
    ids.emplace_back(eval_turn);
3,431✔
1099
    for (int i = 0; i < PassiveBGE::num_passive_bges; ++i)
96,068✔
1100
        ids.emplace_back(your_bg_effects[i]);
92,637✔
1101
    for (int i = 0; i < PassiveBGE::num_passive_bges; ++i)
96,068✔
1102
        ids.emplace_back(enemy_bg_effects[i]);
92,637✔
1103
    if (your_bg_skills.size() > 0)
3,431✔
1104
    {
1105
        auto bge = your_bg_skills[0];
1✔
1106
        ids.emplace_back(bge.id);
1✔
1107
        ids.emplace_back(bge.x);
1✔
1108
        ids.emplace_back(bge.y);
1✔
1109
        ids.emplace_back(bge.n);
1✔
1110
        ids.emplace_back(bge.c);
1✔
1111
        ids.emplace_back(bge.s);
1✔
1112
        ids.emplace_back(bge.s2);
1✔
1113
        ids.emplace_back(bge.all);
1✔
1114
        ids.emplace_back(bge.card_id);
1✔
1115
    }
1116
    else
1117
    {
1118
        // emplace back 9 zeros
1119
        for (int i = 0; i < 9; ++i)
34,300✔
1120
            ids.emplace_back(0);
30,870✔
1121
    }
1122
    if (enemy_bg_skills.size() > 0)
3,431✔
1123
    {
1124
        auto bge = enemy_bg_skills[0];
1✔
1125
        ids.emplace_back(bge.id);
1✔
1126
        ids.emplace_back(bge.x);
1✔
1127
        ids.emplace_back(bge.y);
1✔
1128
        ids.emplace_back(bge.n);
1✔
1129
        ids.emplace_back(bge.c);
1✔
1130
        ids.emplace_back(bge.s);
1✔
1131
        ids.emplace_back(bge.s2);
1✔
1132
        ids.emplace_back(bge.all);
1✔
1133
        ids.emplace_back(bge.card_id);
1✔
1134
    }
1135
    else
1136
    {
1137
        // emplace back 9 zeros
1138
        for (int i = 0; i < 9; ++i)
34,300✔
1139
            ids.emplace_back(0);
30,870✔
1140
    }
1141

1142
    return ids;
3,431✔
1143
}
×
1144

1145
// Compute field hash from two decks
1146
std::string Process::partial_hash()
3,430✔
1147
{
1148
    // TODO fail if quest enabled since, db caching is incompatible with quest mode
1149
    std::stringstream ios;
3,430✔
1150
    for (auto id : partial_ids())
315,560✔
1151
        encode_id_ext_b64(ios, id);
315,560✔
1152

1153
    return ios.str();
6,860✔
1154
}
3,430✔
1155

1156
inline std::vector<Results<uint64_t>> SimulationData::evaluate(const std::vector<bool> &deck_mask)
1,126,127✔
1157
{
1158
    std::vector<Results<uint64_t>> res;
1,126,127✔
1159
    res.reserve(enemy_hands.size() * your_hands.size());
1,126,127✔
1160
    assert(deck_mask.size() == your_hands.size() * enemy_hands.size());
1,126,127✔
1161
    int i = 0;
1162
    for (Hand *your_hand : your_hands)
2,273,554✔
1163
    {
1164
        for (Hand *enemy_hand : enemy_hands)
2,460,054✔
1165
        {
1166
            // TUO5 added mask to already computed decks from db
1167
            if (deck_mask[i] != 0)
1,312,627✔
1168
            {
1169
                your_hand->reset(re);
1,312,627✔
1170
                enemy_hand->reset(re);
1,312,627✔
1171
                Field fd(re, cards, *your_hand, *enemy_hand, gamemode, optimization_mode,
1,312,627✔
1172
#ifndef NQUEST
1173
                         quest,
1174
#endif
1175
                         your_bg_effects, enemy_bg_effects, your_bg_skills, enemy_bg_skills, fixes, flexible_iter, flexible_turn, eval_iter, eval_turn);
1,312,627✔
1176
                Results<uint64_t> result(play(&fd));
1,312,627✔
1177
                if (__builtin_expect(mode_open_the_deck, false))
1,312,627✔
1178
                {
1179
                    // are there remaining (unopened) cards?
1180
                    if (fd.players[1]->deck->shuffled_cards.size())
×
1181
                    {
1182
                        // apply min score (there are unopened cards, so mission failed)
1183
                        result.points = min_possible_score[(size_t)optimization_mode];
×
1184
                    }
1185
                }
1186
                res.emplace_back(result);
1,312,627✔
1187
            }
1,312,627✔
1188
            else
1189
            {
1190
                res.emplace_back(Results<uint64_t>()); // no change since masked
×
1191
            }
1192
            i++;
1,312,627✔
1193
        }
1194
    }
1195
    // std::cout << std::endl<<  "Deck hash: " << your_hand.deck->hash() << "#"<< std::endl;
1196
    return (res);
1,126,127✔
1197
}
×
1198

1199
inline std::vector<Results<uint64_t>> SimulationData::evaluate()
1200
{
1201
    return evaluate(std::vector<bool>(your_hands.size() * enemy_hands.size(), true));
1202
}
1203
//------------------------------------------------------------------------------
1204

1205
//------------------------------------------------------------------------------
1206
// Process
1207
#ifdef _OPENMP
1208
void Process::openmp_evaluate_reduction(EvaluatedResults &evaluated_results)
1209
{
1210

1211
    std::vector<Results<uint64_t>> save_results(evaluated_results.first);
1212
    std::vector<Results<uint64_t>> results(evaluated_results.first);
1213
#pragma omp declare reduction(VecPlus                            \
1214
                              : std::vector <Results <uint64_t>> \
1215
                              : omp_out = merge(omp_out, omp_in))
1216
#pragma omp parallel default(none) shared(evaluated_results, thread_num_iterations, results)
1217
    {
1218
        SimulationData *sim = threads_data.at(omp_get_thread_num());
1219
        sim->set_decks(this->your_decks, this->enemy_decks);
1220
#pragma omp for reduction(VecPlus \
1221
                          : results) schedule(runtime)
1222
        for (unsigned i = 0; i < thread_num_iterations; ++i)
1223
        {
1224
            if (results.size() == 0)
1225
                results = sim->evaluate(mask); // calculate single sim
1226
            else
1227
            {
1228
                results = merge(results, sim->evaluate(mask)); // calculate single sim
1229
            }
1230
        }
1231
#pragma omp for schedule(runtime)
1232
        for (unsigned i = 0; i < results.size(); ++i)
1233
            evaluated_results.first[i] = results[i]; //+?
1234
    }
1235
    evaluated_results.second += thread_num_iterations;
1236
}
1237

1238
void Process::openmp_compare_reduction(EvaluatedResults &evaluated_results)
1239
{
1240

1241
    const unsigned c_num_threads = opt_num_threads;
1242
    std::vector<Results<uint64_t>> save_results(evaluated_results.first);
1243
    std::vector<Results<uint64_t>> results(evaluated_results.first);
1244
    bool compare_stop{false};
1245
    unsigned trials[c_num_threads] = {0};
1246
    long double successes[c_num_threads] = {.0};
1247
    const long double max_possible = max_possible_score[(size_t)optimization_mode];
1248
    const long double prob = 1 - confidence_level;
1249
    omp_lock_t locks[c_num_threads];
1250
    for (unsigned i = 0; i < c_num_threads; ++i)
1251
        omp_init_lock(&locks[i]);
1252
#pragma omp declare reduction(VecPlus                            \
1253
                              : std::vector <Results <uint64_t>> \
1254
                              : omp_out = merge(omp_out, omp_in))
1255
#pragma omp parallel default(none) shared(evaluated_results, trials, successes, locks, compare_stop, thread_num_iterations, results, \
1256
                                          thread_best_results, min_increment_of_score)
1257
    {
1258
        SimulationData *sim = threads_data.at(omp_get_thread_num());
1259
        sim->set_decks(this->your_decks, this->enemy_decks);
1260
        const long double sim_accum = std::accumulate(sim->factors.begin(), sim->factors.end(), .0);
1261
#pragma omp for reduction(VecPlus \
1262
                          : results) schedule(runtime)
1263
        for (unsigned i = 0; i < thread_num_iterations; ++i)
1264
        {
1265
#pragma omp cancellation point for
1266
            if (!compare_stop)
1267
            {
1268
                if (results.size() == 0)
1269
                    results = sim->evaluate(mask); // calculate single sim
1270
                else
1271
                    results = merge(results, sim->evaluate(mask)); // calculate single sim
1272
                long double score_accum_d = 0.0;
1273
                for (unsigned j = 0; j < results.size(); ++j)
1274
                    score_accum_d += results[j].points * sim->factors[j];
1275
                score_accum_d /= sim_accum * max_possible;
1276
                omp_set_lock(&locks[omp_get_thread_num()]);
1277
                trials[omp_get_thread_num()]++;
1278
                successes[omp_get_thread_num()] = score_accum_d;
1279
                omp_unset_lock(&locks[omp_get_thread_num()]);
1280
                // #pragma omp master
1281
                if (omp_get_thread_num() == 0)
1282
                {
1283

1284
                    unsigned ttrials = 0;
1285
                    long double ssuccesses = 0.0;
1286
                    for (unsigned l = 0; l < c_num_threads; ++l)
1287
                    {
1288
                        omp_set_lock(&locks[l]);
1289
                        ttrials += trials[l];
1290
                        ssuccesses += successes[l];
1291
                        omp_unset_lock(&locks[l]);
1292
                    }
1293
                    if (ssuccesses > ttrials)
1294
                    {
1295
                        ssuccesses = ttrials;
1296
                        printf("WARNING: biominal successes {%Le} > trials {%d} in Threads\n", ssuccesses, ttrials);
1297
                        //_DEBUG_MSG(2,"WARNING: biominal successes > trials in Threads");
1298
                    }
1299
                    compare_stop = (boost::math::binomial_distribution<>::find_upper_bound_on_p(ttrials, ssuccesses, prob) * max_possible <
1300
                                    thread_best_results->points + min_increment_of_score);
1301
                    if (compare_stop)
1302
                    {
1303
#pragma omp cancel for
1304
                    }
1305
                }
1306
            }
1307
        }
1308
#pragma omp for schedule(runtime)
1309
        for (unsigned i = 0; i < results.size(); ++i)
1310
            evaluated_results.first[i] = results[i]; //+?
1311
    }
1312
    evaluated_results.second += thread_num_iterations;
1313
    for (unsigned i = 0; i < c_num_threads; ++i)
1314
        omp_destroy_lock(&locks[i]);
1315
}
1316

1317
std::vector<Results<uint64_t>> merge(std::vector<Results<uint64_t>> out, std::vector<Results<uint64_t>> in)
1318
{
1319
    // printf("merging out: %d in: %d \n", (int)out[0].wins, (int)in[0].wins);
1320
    // printf("out%p ",(void *)&out );
1321
    for (unsigned i = 0; i < out.size(); ++i)
1322
        out[i] += in[i];
1323
    // printf("merged out: %d \n", (int)out[0].wins);
1324
    return out;
1325
}
1326
#endif
1327

1328
inline bool Process::eval_ml(unsigned num_iterations, EvaluatedResults &evaluated_results)
3,426✔
1329
{
1330
    if (!use_ml)
3,426✔
1331
        return true;
1332
    bool run = false;
1✔
1333
    auto sampls = samples();
1✔
1334
    // iterate results from samples()
1335
    for (unsigned i = 0; i < sampls.size(); i++)
2✔
1336
    {
1337
        // already found in db?
1338
        if (mask[i])
1✔
1339
        {
1340
            auto s = sampls[i];
1✔
1341
            auto wins = atof(win_model.predict(s).c_str());
1✔
1342
            auto losses = atof(loss_model.predict(s).c_str());
1✔
1343
            auto draws = atof(stall_model.predict(s).c_str());
1✔
1344
            auto points = atof(points_model.predict(s).c_str());
1✔
1345
            if (wins < 0)
1✔
1346
                wins = 0;
1347
            if (losses < 0)
1✔
1348
                losses = 0;
1349
            if (draws < 0)
1✔
1350
                draws = 0;
1351
            if (points < 0)
1✔
1352
                points = 0;
1353
            // if((wins + losses + draws) > 1.05 || (wins + losses + draws) < 0.95) {
1354
            //     std::cout << "WARNING: ML model returned invalid sample: " << wins << " "  << draws << " " <<losses << " points " << points << std::endl;
1355
            // }
1356
            if (use_only_ml || ((wins + losses + draws) < 1.00 + ml_precision && (wins + losses + draws) > 1.00 - ml_precision)) // we use the sum of wins, losses and draws to determine if the sample is valid
1✔
1357
            {
1358
                // std::cout << "ML model returned: " << wins << " "  << draws << " " <<losses << " points " << points << " " << num_iterations << std::endl;
1359
                mask[i] = false;
1✔
1360
                Results<uint64_t> r = {
1✔
1361
                    static_cast<uint64_t>(std::round((1.0 * wins * num_iterations / (wins + losses + draws)))),
1✔
1362
                    static_cast<uint64_t>(std::round((1.0 * draws * num_iterations / (wins + losses + draws)))),
1✔
1363
                    static_cast<uint64_t>(std::round((1.0 * losses * num_iterations / (wins + losses + draws)))),
1✔
1364
                    static_cast<uint64_t>(std::round((1.0 * points * num_iterations))),
1✔
1365
                    static_cast<uint64_t>(num_iterations)};
1✔
1366
                evaluated_results.first[i] = r;
1✔
1367
            }
1✔
1368
            else
1369
            {
1370
                mask[i] = true;
×
1371
                run = true;
×
1372
            }
1373
        }
1✔
1374
    }
1375

1376
    return run;
1✔
1377
}
1✔
1378

1379
inline bool Process::check_db(std::vector<std::array<std::string, 3>> const &vhashes, unsigned num_iterations, EvaluatedResults &evaluated_results)
3,430✔
1380
{
1381
    if (!use_db_load)
3,430✔
1382
        return true;
1383
    bool run = false;
1384
    // TODO TUO5 check db for results
1385
    //  TODO TUO5 calculate mask for sim evaluate based on missing results in db
1386
    for (unsigned i = 0; i < vhashes.size(); i++)
2,100✔
1387
    {
1388
        bool found = false;
2,038✔
1389
        std::string hash = vhashes[i][0];
2,038✔
1390
        // TODO TUO5 check db for hash
1391
        auto it1 = database.find(hash);
2,038✔
1392
        std::map<std::string, Results<uint64_t>>::iterator it;
2,038✔
1393
        if (it1 != database.end())
2,038✔
1394
        {
1395
            auto it2 = it1->second.find(vhashes[i][1]);
600✔
1396
            if (it2 != it1->second.end())
600✔
1397
            {
1398
                auto it3 = it2->second.find(vhashes[i][2]);
116✔
1399
                if (it3 != it2->second.end())
116✔
1400
                {
1401
                    it = it3;
115✔
1402
                    found = true;
115✔
1403
                    // std::cout << "hit databse" << std::endl;
1404
                }
1405
            }
1406
        }
1407
        // TODO TUO5 if hash not in db, add to mask
1408
        if (!found)
115✔
1409
        {
1410
            mask[i] = true;
1,923✔
1411
            run = true;
1,923✔
1412
        }
1413
        else
1414
        {
1415
            if (it->second.count >= num_iterations)
115✔
1416
            {
1417
                mask[i] = false;
115✔
1418
                // TUO5 load results from db
1419
                // renormalized to asked num_iterations
1420
                Results<uint64_t> r = {
115✔
1421
                    static_cast<uint64_t>(std::round((1.0 * it->second.wins * num_iterations) / it->second.count)),
115✔
1422
                    static_cast<uint64_t>(std::round((1.0 * it->second.draws * num_iterations) / it->second.count)),
115✔
1423
                    static_cast<uint64_t>(std::round((1.0 * it->second.losses * num_iterations) / it->second.count)),
115✔
1424
                    static_cast<uint64_t>(std::round((1.0 * it->second.points * num_iterations) / it->second.count)),
115✔
1425
                    static_cast<uint64_t>(num_iterations)};
115✔
1426
                evaluated_results.first[i] = r;
115✔
1427
            }
1428
            else
1429
            {
1430
                mask[i] = true;
×
1431
                run = true;
×
1432
            }
1433
        }
1434
    }
2,038✔
1435
    return run;
1436
}
1437

1438
inline void Process::save_db(std::vector<std::array<std::string, 3>> const &vhashes, EvaluatedResults &evaluated_results)
3,425✔
1439
{
1440
    // TODO TUO5 save results to db
1441
    for (unsigned i = 0; i < vhashes.size(); i++)
8,715✔
1442
    {
1443
        if (mask[i])
5,290✔
1444
        {
1445
            std::array<std::string, 3> hash = vhashes[i];
5,290✔
1446
            // check if map is initialized otherwise initialize
1447
            if (database.find(hash[0]) == database.end())
5,290✔
1448
            {
1449
                database[hash[0]] = std::map<std::string, std::map<std::string, Results<uint64_t>>>();
49✔
1450
            }
1451
            if (database[hash[0]].find(hash[1]) == database[hash[0]].end())
5,290✔
1452
            {
1453
                database[hash[0]][hash[1]] = std::map<std::string, Results<uint64_t>>();
2,678✔
1454
            }
1455
            database[hash[0]][hash[1]][hash[2]] = evaluated_results.first[i];
5,290✔
1456
        }
5,290✔
1457
    }
1458
}
3,425✔
1459

1460
EvaluatedResults &Process::evaluate(unsigned num_iterations, EvaluatedResults &evaluated_results)
366✔
1461
{
1462
    if (num_iterations <= evaluated_results.second)
366✔
1463
    {
1464
        return evaluated_results;
1465
    }
1466
    std::vector<std::array<std::string, 3>> vhashes = hashes();
205✔
1467
    // fill mask with true
1468
    mask = std::vector<bool>(vhashes.size(), true);
205✔
1469
    if (!check_db(vhashes, num_iterations, evaluated_results))
205✔
1470
    {
1471
        // 100% covered by db
1472
        evaluated_results.second = num_iterations;
4✔
1473
        return evaluated_results;
4✔
1474
    }
1475
    if (!eval_ml(num_iterations, evaluated_results))
201✔
1476
    {
1477
        // ml is good
1478
        evaluated_results.second = num_iterations;
1✔
1479
        return evaluated_results;
1✔
1480
    }
1481

1482
    thread_num_iterations = num_iterations - evaluated_results.second;
200✔
1483
    thread_results = &evaluated_results;
200✔
1484
    thread_compare = false;
200✔
1485
#ifndef _OPENMP
1486
    // unlock all the threads
1487
    main_barrier.wait();
200✔
1488
    // wait for the threads
1489
    main_barrier.wait();
200✔
1490
#else
1491
    openmp_evaluate_reduction(evaluated_results);
1492
#endif
1493
    save_db(vhashes, evaluated_results);
200✔
1494

1495
    return evaluated_results;
1496
}
205✔
1497

1498
EvaluatedResults &Process::compare(unsigned num_iterations, EvaluatedResults &evaluated_results, const FinalResults<long double> &best_results)
3,513✔
1499
{
1500
    if (num_iterations <= evaluated_results.second)
3,513✔
1501
    {
1502
        return evaluated_results;
1503
    }
1504
    std::vector<std::array<std::string, 3>> vhashes = hashes();
3,225✔
1505
    // fill mask with true
1506
    mask = std::vector<bool>(vhashes.size(), true);
3,225✔
1507
    if (!check_db(vhashes, num_iterations, evaluated_results))
3,225✔
1508
    {
1509
        // 100% covered by db
1510
        evaluated_results.second = num_iterations;
×
1511
        return evaluated_results;
×
1512
    }
1513
    if (!eval_ml(num_iterations, evaluated_results))
3,225✔
1514
    {
1515
        // ml is good
1516
        evaluated_results.second = num_iterations;
×
1517
        return evaluated_results;
×
1518
    }
1519
    thread_num_iterations = num_iterations - evaluated_results.second;
3,225✔
1520
    thread_results = &evaluated_results;
3,225✔
1521
    thread_best_results = &best_results;
3,225✔
1522
    thread_compare = true;
3,225✔
1523
    thread_compare_stop = false;
3,225✔
1524
#ifndef _OPENMP
1525
    // unlock all the threads
1526
    main_barrier.wait();
3,225✔
1527
    // wait for the threads
1528
    main_barrier.wait();
3,225✔
1529
#else
1530
    openmp_compare_reduction(evaluated_results);
1531
#endif
1532
    save_db(vhashes, evaluated_results);
3,225✔
1533
    return evaluated_results;
1534
}
3,225✔
1535
//------------------------------------------------------------------------------
1536
void thread_evaluate(boost::barrier &main_barrier,
81✔
1537
                     boost::mutex &shared_mutex,
1538
                     SimulationData &sim,
1539
                     const Process &p,
1540
                     unsigned thread_id)
1541
{
1542
#ifndef _OPENMP
1543
    while (true)
6,931✔
1544
    {
1545
        main_barrier.wait();
3,506✔
1546
        sim.set_decks(p.your_decks, p.enemy_decks);
3,506✔
1547
        if (destroy_threads)
3,506✔
1548
        {
1549
            return;
81✔
1550
        }
1551
        while (true)
2,255,679✔
1552
        {
1553
            shared_mutex.lock();                                                       //<<<<
1,129,552✔
1554
            if (thread_num_iterations == 0 || (thread_compare && thread_compare_stop)) //!
1,129,552✔
1555
            {
1556
                shared_mutex.unlock(); //>>>>
3,425✔
1557
                main_barrier.wait();
3,425✔
1558
                break;
3,425✔
1559
            }
1560
            else
1561
            {
1562
                --thread_num_iterations; //!
1,126,127✔
1563
                shared_mutex.unlock();   //>>>>
1,126,127✔
1564
                std::vector<Results<uint64_t>> result{sim.evaluate(p.mask)};
1,126,127✔
1565
                shared_mutex.lock();                                                        //<<<<
1,126,127✔
1566
                std::vector<uint64_t> thread_score_local(thread_results->first.size(), 0u); //!
1,126,127✔
1567
                unsigned counter = 0;
1,126,127✔
1568
                for (unsigned index(0); index < result.size(); ++index)
2,438,754✔
1569
                {
1570
                    thread_results->first[index] += result[index]; //!
1,312,627✔
1571
                    if (counter == 0 || thread_results->first[index].count < counter)
1,312,627✔
1572
                    {
1573
                        counter = thread_results->first[index].count;
1,126,127✔
1574
                    }
1575
                }
1576
                for (unsigned index(0); index < result.size(); ++index)
2,438,754✔
1577
                {
1578
                    // TUO5 we weight scores by the number of times a deck is used, since the results are no langer same sized in partially cached case
1579
                    thread_score_local[index] = (thread_results->first[index].points * (counter)) / thread_results->first[index].count; //!
1,312,627✔
1580
                                                                                                                                        // thread_score_local[index] = static_cast<uint64_t>(std::round((thread_results->first[index].points * (counter))/thread_results->first[index].count)); //!
1581
                }
1582
                ++thread_results->second;                            //!
1,126,127✔
1583
                unsigned thread_total_local{thread_results->second}; //!
1,126,127✔
1584
                shared_mutex.unlock();                               //>>>>
1,126,127✔
1585
                if (thread_compare && thread_id == 0 && thread_total_local > 1)
1,126,127✔
1586
                {
1587
                    unsigned score_accum = 0;
780,959✔
1588
                    // Multiple defense decks case: scaling by factors and approximation of a "discrete" number of events.
1589
                    if (result.size() > 1)
780,959✔
1590
                    {
1591
                        long double score_accum_d = 0.0;
1592
                        for (unsigned i = 0; i < thread_score_local.size(); ++i)
×
1593
                        {
1594
                            score_accum_d += thread_score_local[i] * sim.factors[i];
×
1595
                        }
1596
                        score_accum_d /= std::accumulate(sim.factors.begin(), sim.factors.end(), .0);
×
1597
                        score_accum = score_accum_d;
×
1598
                    }
1599
                    else
1600
                    {
1601
                        score_accum = thread_score_local[0];
780,959✔
1602
                    }
1603
                    bool compare_stop(false);
780,959✔
1604
                    long double max_possible = max_possible_score[(size_t)optimization_mode];
780,959✔
1605

1606
                    // APN
1607
                    auto trials = thread_total_local;
780,959✔
1608
                    auto prob = 1 - confidence_level;
780,959✔
1609
                    auto successes = score_accum / max_possible;
780,959✔
1610
                    if (successes > trials)
780,959✔
1611
                    {
1612
                        std::cout << "WARNING: biominal successes > trials in Threads" << successes << " " << trials << " " << counter << std::endl;
×
1613
                        _DEBUG_MSG(2, "WARNING: biominal successes > trials in Threads");
×
1614
                        successes = trials;
1,126,127✔
1615
                    }
1616
                    else
1617
                    {
1618

1619
                        // Get a loose (better than no) upper bound. TODO: Improve it.
1620
                        compare_stop = (boost::math::binomial_distribution<>::find_upper_bound_on_p(trials, successes, prob) * max_possible <
780,959✔
1621
                                        thread_best_results->points + min_increment_of_score);
780,959✔
1622
                        if (compare_stop)
780,959✔
1623
                        {
1624
                            shared_mutex.lock(); //<<<<
2,771✔
1625
                            // std::cout << thread_total_local << "\n";
1626
                            thread_compare_stop = true; //!
2,771✔
1627
                            shared_mutex.unlock();      //>>>>
2,771✔
1628
                        }
1629
                    }
1630
                }
1631
            }
1,126,127✔
1632
        }
1,126,127✔
1633
    }
3,425✔
1634
#endif
1635
}
1636
//------------------------------------------------------------------------------
1637
void print_score_info(const EvaluatedResults &results, std::vector<long double> &factors)
39✔
1638
{
1639
    auto final = compute_score(results, factors);
39✔
1640
    std::cout << final.points << " (";
39✔
1641
    if (!simplify_output)
39✔
1642
    {
1643
        if (show_ci)
39✔
1644
        {
1645
            std::cout << final.points_lower_bound << "-" << final.points_upper_bound << ", ";
×
1646
        }
1647
        for (const auto &val : results.first)
78✔
1648
        {
1649
            switch (optimization_mode)
39✔
1650
            {
1651
            case OptimizationMode::raid:
×
1652
            case OptimizationMode::campaign:
×
1653
            case OptimizationMode::brawl:
×
1654
            case OptimizationMode::brawl_defense:
×
1655
            case OptimizationMode::war:
×
1656
            case OptimizationMode::war_defense:
×
1657
#ifndef NQUEST
1658
            case OptimizationMode::quest:
1659
#endif
1660
                std::cout << val.points << " ";
×
1661
                break;
1662
            default:
39✔
1663
                std::cout << val.points / 100 << " ";
39✔
1664
                break;
1665
            }
1666
        }
1667
    }
1668
    else
1669
    {
1670
        std::cout << "...";
×
1671
    }
1672
    std::cout << "/ " << results.second << ")" << std::endl;
39✔
1673
}
39✔
1674
//------------------------------------------------------------------------------
1675
void print_results(const EvaluatedResults &results, std::vector<long double> &factors)
76✔
1676
{
1677
    auto final = compute_score(results, factors);
76✔
1678
    std::cout << "win%: " << final.wins * 100.0 << " (";
76✔
1679
    if (!simplify_output)
76✔
1680
    {
1681
        for (const auto &val : results.first)
2,128✔
1682
        {
1683
            std::cout << val.wins << " ";
2,052✔
1684
        }
1685
    }
1686
    else
1687
    {
1688
        std::cout << "...";
×
1689
    }
1690
    std::cout << "/ " << results.second << ")" << std::endl;
76✔
1691

1692
    std::cout << "stall%: " << final.draws * 100.0 << " (";
76✔
1693
    if (!simplify_output)
76✔
1694
    {
1695
        for (const auto &val : results.first)
2,128✔
1696
        {
1697
            std::cout << val.draws << " ";
2,052✔
1698
        }
1699
    }
1700
    else
1701
    {
1702
        std::cout << "...";
×
1703
    }
1704
    std::cout << "/ " << results.second << ")" << std::endl;
76✔
1705

1706
    std::cout << "loss%: " << final.losses * 100.0 << " (";
76✔
1707
    if (!simplify_output)
76✔
1708
    {
1709
        for (const auto &val : results.first)
2,128✔
1710
        {
1711
            std::cout << val.losses << " ";
2,052✔
1712
        }
1713
    }
1714
    else
1715
    {
1716
        std::cout << "...";
×
1717
    }
1718
    std::cout << "/ " << results.second << ")" << std::endl;
76✔
1719

1720
#ifndef NQUEST
1721
    if (optimization_mode == OptimizationMode::quest)
1722
    {
1723
        // points = win% * win_score + (must_win ? win% : 100%) * quest% * quest_score
1724
        // quest% = (points - win% * win_score) / (must_win ? win% : 100%) / quest_score
1725
        std::cout << "quest%: " << (final.points - final.wins * tuo::quest.win_score) / (tuo::quest.must_win ? final.wins : 1) / tuo::quest.quest_score * 100 << std::endl;
1726
    }
1727
#endif
1728

1729
    unsigned min_score = min_possible_score[(size_t)optimization_mode];
76✔
1730
    unsigned max_score = max_possible_score[(size_t)optimization_mode];
76✔
1731
    switch (optimization_mode)
76✔
1732
    {
1733
    case OptimizationMode::raid:
×
1734
    case OptimizationMode::campaign:
×
1735
    case OptimizationMode::brawl:
×
1736
    case OptimizationMode::brawl_defense:
×
1737
    case OptimizationMode::war:
×
1738
    case OptimizationMode::war_defense:
×
1739
#ifndef NQUEST
1740
    case OptimizationMode::quest:
1741
#endif
1742
        std::cout << "score: " << final.points;
×
1743
        if (optimization_mode == OptimizationMode::brawl)
×
1744
        {
1745
            auto win_points = final.wins ? ((final.points - min_score * (1.0 - final.wins)) / final.wins) : final.points;
×
1746
            std::cout << " [" << win_points << " per win]";
×
1747
        }
1748
        else if (optimization_mode == OptimizationMode::brawl_defense)
×
1749
        {
1750
            auto opp_win_points = final.losses ? max_score - ((final.points - (max_score - min_score) * (1.0 - final.losses)) / final.losses) : final.points;
×
1751
            std::cout << " [" << opp_win_points << " per opp win]";
×
1752
        }
1753
        else if (optimization_mode == OptimizationMode::war)
×
1754
        {
1755
            auto win_points = final.wins ? ((final.points - min_score * (1.0 - final.wins)) / final.wins) : final.points;
×
1756
            std::cout << " [" << win_points << " per win]";
×
1757
        }
1758
        else if (optimization_mode == OptimizationMode::war_defense)
×
1759
        {
1760
            auto opp_win_points = final.losses ? max_score - ((final.points - (max_score - min_score) * (1.0 - final.losses)) / final.losses) : final.points;
×
1761
            std::cout << " [" << opp_win_points << " per opp win]";
×
1762
        }
1763
        std::cout << " (";
×
1764
        if (!simplify_output)
×
1765
        {
1766
            for (const auto &val : results.first)
×
1767
            {
1768
                std::cout << val.points << " ";
×
1769
            }
1770
        }
1771
        else
1772
        {
1773
            std::cout << "...";
×
1774
        }
1775
        std::cout << "/ " << results.second << ")" << std::endl;
×
1776
        if (show_ci)
×
1777
        {
1778
            std::cout << "ci: " << final.points_lower_bound << " - " << final.points_upper_bound << std::endl;
×
1779
        }
1780
        break;
1781
    default:
1782
        break;
1783
    }
1784
}
76✔
1785

1786
//------------------------------------------------------------------------------
1787
// Prints which cards have been upgraded
1788
void print_upgraded_cards(Deck *deck)
5✔
1789
{
1790
    if (!print_upgraded)
5✔
1791
        return;
5✔
1792
    auto owned_cards_c = owned_cards;
×
1793
    std::cout << "Upgraded Cards: ";
×
1794
    auto cards = deck->cards;
×
1795
    cards.insert(cards.begin(), deck->commander);
×
1796
    for (const Card *card : cards)
×
1797
    {
1798

1799
        if ((card->m_set == 1000) && (card->m_rarity <= 2) && (card->is_low_level_card()))
×
1800
        {
1801
            continue;
×
1802
        }
1803

1804
        if ((card->m_id == 50002) || (card->m_category == CardCategory::dominion_material))
×
1805
        {
1806
            continue;
×
1807
        }
1808

1809
        if ((fund || (card->m_category != CardCategory::normal)) && (owned_cards_c[card->m_id] == 0))
×
1810
        {
1811
            // print card
1812
            std::cout << card->m_name << ", ";
×
1813
        }
1814
        if (owned_cards_c[card->m_id] > 0)
×
1815
            owned_cards_c[card->m_id]--;
×
1816
    }
1817
    std::cout << std::endl;
×
1818
}
×
1819
//------------------------------------------------------------------------------
1820
void print_cards_inline(std::vector<const Card *> cards, std::ostream &os, Deck *deck)
1,315✔
1821
{
1822
    std::string last_name = "";
1,315✔
1823
    unsigned num_repeat(0);
1,315✔
1824
    bool first = true;
1,315✔
1825

1826
    // for (const Card* card: cards)
1827
    //{
1828
    for (unsigned i = 0; i < cards.size(); ++i)
13,940✔
1829
    {
1830
        auto card = cards[i];
12,625✔
1831

1832
        if (deck == nullptr && card->m_name == last_name)
12,625✔
1833
        {
1834
            ++num_repeat;
×
1835
        }
1836
        else
1837
        {
1838
            if (num_repeat > 1)
12,625✔
1839
            {
1840
                os << " #" << num_repeat;
×
1841
            }
1842
            if (deck != nullptr)
12,625✔
1843
                os << (first ? "" : ", ") << (deck->card_marks[i] == '!' ? "!" : "") << card->m_name;
1,658✔
1844
            else
1845
                os << (first ? "" : ", ") << card->m_name;
22,860✔
1846
            first = false;
12,625✔
1847
            last_name = card->m_name;
25,250✔
1848
            num_repeat = 1;
1849
        }
1850
    }
1851
    if (num_repeat > 1)
1,315✔
1852
    {
1853
        os << " #" << num_repeat;
×
1854
    }
1855
    os << "\n";
1,315✔
1856
}
1,315✔
1857
void print_score_inline(const FinalResults<long double> score)
1,234✔
1858
{
1859
    // print optimization result details
1860
    switch (optimization_mode)
1,234✔
1861
    {
1862
    case OptimizationMode::raid:
×
1863
    case OptimizationMode::campaign:
×
1864
    case OptimizationMode::brawl:
×
1865
    case OptimizationMode::brawl_defense:
×
1866
    case OptimizationMode::war:
×
1867
    case OptimizationMode::war_defense:
×
1868
#ifndef NQUEST
1869
    case OptimizationMode::quest:
1870
#endif
1871
        std::cout << "(" << score.wins * 100 << "% win";
×
1872
#ifndef NQUEST
1873
        if (optimization_mode == OptimizationMode::quest)
1874
        {
1875
            std::cout << ", " << (score.points - score.wins * tuo::quest.win_score) / (tuo::quest.must_win ? score.wins : 1) / tuo::quest.quest_score * 100 << "% quest";
1876
        }
1877
#endif
1878
        if (show_ci)
×
1879
        {
1880
            std::cout << ", " << score.points_lower_bound << "-" << score.points_upper_bound;
×
1881
        }
1882
        std::cout << ") ";
×
1883
        break;
×
1884
    case OptimizationMode::defense:
×
1885
        std::cout << "(" << score.draws * 100.0 << "% stall) ";
×
1886
        break;
×
1887
    default:
1888
        break;
1889
    }
1890
    std::cout << score.points;
1,234✔
1891
    unsigned min_score = min_possible_score[(size_t)optimization_mode];
1,234✔
1892
    unsigned max_score = max_possible_score[(size_t)optimization_mode];
1,234✔
1893
    if (optimization_mode == OptimizationMode::brawl)
1,234✔
1894
    {
1895
        auto win_points = score.wins ? ((score.points - min_score * (1.0 - score.wins)) / score.wins) : score.points;
×
1896
        std::cout << " [" << win_points << " per win]";
×
1897
    }
1898
    else if (optimization_mode == OptimizationMode::brawl_defense)
1,234✔
1899
    {
1900
        auto opp_win_points = score.losses ? max_score - ((score.points - (max_score - min_score) * (1.0 - score.losses)) / score.losses) : score.points;
×
1901
        std::cout << " [" << opp_win_points << " per opp win]";
×
1902
    }
1903
    else if (optimization_mode == OptimizationMode::war)
1,234✔
1904
    {
1905
        auto win_points = score.wins ? ((score.points - min_score * (1.0 - score.wins)) / score.wins) : score.points;
×
1906
        std::cout << " [" << win_points << " per win]";
×
1907
    }
1908
    else if (optimization_mode == OptimizationMode::war_defense)
1,234✔
1909
    {
1910
        auto opp_win_points = score.losses ? max_score - ((score.points - (max_score - min_score) * (1.0 - score.losses)) / score.losses) : score.points;
×
1911
        std::cout << " [" << opp_win_points << " per opp win]";
×
1912
    }
1913
}
1,234✔
1914
//------------------------------------------------------------------------------
1915
// Calculates and prints individual card value in this deck
1916
void print_sim_card_values(Deck *original_deck, Process &p, unsigned iter) // run_deck == p.your_decks[0]
81✔
1917
{
1918
    if (!print_values && vc_x == 0)
81✔
1919
        return;
81✔
1920
    if (p.your_decks.size() != 1)
×
1921
        return; // only for single deck
1922
    // auto deck = original_deck;
1923
    if (original_deck->strategy == DeckStrategy::random || original_deck->strategy == DeckStrategy::flexible || original_deck->strategy == DeckStrategy::evaluate || original_deck->strategy == DeckStrategy::evaluate_twice)
×
1924
    {
1925
        std::sort(original_deck->cards.begin(), original_deck->cards.end(), [](const Card *a, const Card *b)
×
1926
                  { return a->m_id < b->m_id; });
×
1927
    }
1928
    std::string last_name;
×
1929
    long double score;
×
1930
    Deck *sim_deck = p.your_decks[0];
×
1931
    Deck *your_deck = original_deck->clone(); // save the original deck
×
1932
    auto cards = your_deck->cards;
×
1933
    // sim original/base result
1934
    sim_deck->commander = your_deck->commander;
×
1935
    sim_deck->cards = your_deck->cards;
×
1936
    sim_deck->alpha_dominion = your_deck->alpha_dominion;
×
1937
    EvaluatedResults results = {EvaluatedResults::first_type(p.enemy_decks.size() * p.your_decks.size()), 0};
×
1938
    results = p.evaluate(iter, results);
×
1939
    const FinalResults<long double> fr_base = compute_score(results, p.factors);
×
1940
    long double base = fr_base.points;
×
1941
    std::multimap<long double, std::string> msn;
×
1942
    std::multimap<std::string, long double> mns;
×
1943
    for (unsigned i = 0; i < cards.size(); ++i)
×
1944
    {
1945
        auto card = cards[i];
×
1946
        // if (card->m_name == last_name)
1947
        //{
1948
        // }
1949
        // else
1950
        //{
1951
        last_name = card->m_name;
×
1952
        // sim it
1953
        sim_deck->cards = your_deck->cards;                 // reset cards
×
1954
        sim_deck->cards.erase(sim_deck->cards.begin() + i); // remove to test card
×
1955
        EvaluatedResults results = {EvaluatedResults::first_type(p.enemy_decks.size() * p.your_decks.size()), 0};
×
1956
        results = p.evaluate(iter, results);
×
1957
        const FinalResults<long double> fr = compute_score(results, p.factors);
×
1958
        score = base - fr.points; // subtract from result to get value
×
1959
        // std::cout << card->m_name << " (" << score << "), ";
1960
        msn.insert(std::make_pair(score, card->m_name));
×
1961
        mns.insert(std::make_pair(card->m_name, score));
×
1962
        //}
1963
    }
×
1964
    sim_deck->cards = your_deck->cards; // reset cards
×
1965
    if (vc_x > 0)
×
1966
    {
1967
        // std::cout << "Locked Deck: ";
1968
        ////std::cout << original_deck->cards.size() << " units: ";
1969
        ////print_score_inline(fr_base);
1970
        ////std::cout <<  ": ";
1971
        // std::cout << original_deck->commander->m_name;
1972
        //  print dominions
1973
        // if (original_deck->alpha_dominion)
1974
        //{ std::cout << ", " << original_deck->alpha_dominion->m_name; }
1975

1976
        for (unsigned i = 0; i < cards.size(); ++i)
×
1977
        {
1978
            original_deck->card_marks.erase(i);
×
1979
            if (vc_x >= std::distance(msn.find(mns.find(cards[i]->m_name)->second), msn.end())) // or mns.start()
×
1980
            {
1981
                original_deck->card_marks[i] = '!';
×
1982
            }
1983
        }
1984
    }
1985
    if (print_values)
×
1986
    {
1987
        std::cout << "Value of Cards: ";
×
1988
        for (unsigned i = 0; i < cards.size(); ++i)
×
1989
        {
1990
            std::cout << cards[i]->m_name << " (" << mns.find(cards[i]->m_name)->second << "), ";
×
1991
        }
1992
        std::cout << std::endl;
×
1993
    }
1994
}
×
1995
//------------------------------------------------------------------------------
1996
void print_deck_inline(const unsigned deck_cost, const FinalResults<long double> score, Deck *deck, bool print_locked)
1,234✔
1997
{
1998
    // print units count
1999
    std::cout << deck->cards.size() << " units: ";
1,234✔
2000

2001
    // print deck cost (if fund is enabled)
2002
    if (fund > 0)
1,234✔
2003
    {
2004
        std::cout << "$" << deck_cost << " ";
×
2005
    }
2006
    print_score_inline(score);
1,234✔
2007
    /*
2008
    // print optimization result details
2009
    switch(optimization_mode)
2010
    {
2011
    case OptimizationMode::raid:
2012
    case OptimizationMode::campaign:
2013
    case OptimizationMode::brawl:
2014
    case OptimizationMode::brawl_defense:
2015
    case OptimizationMode::war:
2016
    case OptimizationMode::war_defense:
2017
#ifndef NQUEST
2018
case OptimizationMode::quest:
2019
#endif
2020
std::cout << "(" << score.wins * 100 << "% win";
2021
#ifndef NQUEST
2022
if (optimization_mode == OptimizationMode::quest)
2023
{
2024
std::cout << ", " << (score.points - score.wins * quest.win_score) / (quest.must_win ? score.wins : 1) / quest.quest_score * 100 << "% quest";
2025
}
2026
#endif
2027
if (show_ci)
2028
{
2029
std::cout << ", " << score.points_lower_bound << "-" << score.points_upper_bound;
2030
}
2031
std::cout << ") ";
2032
break;
2033
case OptimizationMode::defense:
2034
std::cout << "(" << score.draws * 100.0 << "% stall) ";
2035
break;
2036
default:
2037
break;
2038
}
2039
std::cout << score.points;
2040
unsigned min_score = min_possible_score[(size_t)optimization_mode];
2041
unsigned max_score = max_possible_score[(size_t)optimization_mode];
2042
if (optimization_mode == OptimizationMode::brawl)
2043
{
2044
auto win_points = score.wins ? ((score.points - min_score * (1.0 - score.wins)) / score.wins) : score.points;
2045
std::cout << " [" << win_points << " per win]";
2046
}
2047
else if (optimization_mode == OptimizationMode::brawl_defense)
2048
{
2049
auto opp_win_points = score.losses ? max_score - ((score.points - (max_score - min_score) * (1.0 - score.losses)) / score.losses) : score.points;
2050
std::cout << " [" << opp_win_points << " per opp win]";
2051
}
2052
else if (optimization_mode == OptimizationMode::war)
2053
{
2054
auto win_points = score.wins ? ((score.points - min_score * (1.0 - score.wins)) / score.wins) : score.points;
2055
std::cout << " [" << win_points << " per win]";
2056
}
2057
else if (optimization_mode == OptimizationMode::war_defense)
2058
{
2059
auto opp_win_points = score.losses ? max_score - ((score.points - (max_score - min_score) * (1.0 - score.losses)) / score.losses) : score.points;
2060
std::cout << " [" << opp_win_points << " per opp win]";
2061
}
2062
*/
2063

2064
    // print commander
2065
    std::cout << ": " << deck->commander->m_name;
1,234✔
2066

2067
    // print dominions
2068
    if (deck->alpha_dominion)
1,234✔
2069
    {
2070
        std::cout << ", " << deck->alpha_dominion->m_name;
1,223✔
2071
    }
2072

2073
    // print deck cards
2074
    if (deck->strategy == DeckStrategy::random || deck->strategy == DeckStrategy::flexible || deck->strategy == DeckStrategy::evaluate || deck->strategy == DeckStrategy::evaluate_twice)
1,234✔
2075
    {
2076
        std::sort(deck->cards.begin(), deck->cards.end(), [](const Card *a, const Card *b)
1,234✔
2077
                  { return a->m_id < b->m_id; });
24,100✔
2078
    }
2079
    std::cout << ", ";
1,234✔
2080
    if (print_locked)
1,234✔
2081
        print_cards_inline(deck->cards, std::cout, deck);
10✔
2082
    else
2083
        print_cards_inline(deck->cards, std::cout);
1,224✔
2084
}
1,234✔
2085
//------------------------------------------------------------------------------
2086
bool is_timeout_reached()
307✔
2087
{
2088
    if (__builtin_expect(maximum_time > 0, false))
307✔
2089
    {
2090
        if ((std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - start_time)).count() > maximum_time * 60 * 60)
×
2091
        {
2092
            std::cout << "Time expired! Current result:" << std::endl;
×
2093
            return true;
×
2094
        }
2095
    }
2096
    return false;
2097
}
2098
std::vector<std::vector<const Card *>> get_candidate_lists(Process &proc)
5✔
2099
{
2100
    std::vector<std::vector<const Card *>> ret_candidates;
5✔
2101
    std::vector<const Card *> commander_candidates;
5✔
2102
    std::vector<const Card *> alpha_dominion_candidates;
5✔
2103
    std::vector<const Card *> card_candidates;
5✔
2104

2105
    // resolve available to player cards
2106
    auto player_assaults_and_structures = proc.cards.player_commanders;
5✔
2107
    player_assaults_and_structures.insert(player_assaults_and_structures.end(), proc.cards.player_structures.begin(), proc.cards.player_structures.end());
5✔
2108
    player_assaults_and_structures.insert(player_assaults_and_structures.end(), proc.cards.player_assaults.begin(), proc.cards.player_assaults.end());
5✔
2109
    for (auto it = player_assaults_and_structures.begin(); it != player_assaults_and_structures.end(); ++it)
210,550✔
2110
    {
2111
        const Card *card = *it;
210,545✔
2112
        // skip illegal
2113
        if ((card->m_category != CardCategory::dominion_alpha) && (card->m_category != CardCategory::normal))
210,545✔
2114
        {
2115
            continue;
210,370✔
2116
        }
2117

2118
        // skip dominions when their climbing is disabled
2119
        if ((card->m_category == CardCategory::dominion_alpha) && (!use_owned_dominions))
210,545✔
2120
        {
2121
            continue;
×
2122
        }
2123

2124
        // try to skip a card unless it's allowed
2125
        if (!allowed_candidates.count(card->m_id))
210,545✔
2126
        {
2127
            // skip disallowed always
2128
            if (disallowed_candidates.count(card->m_id))
210,545✔
2129
            {
2130
                continue;
×
2131
            }
2132

2133
            // handle dominions
2134
            if (card->m_category == CardCategory::dominion_alpha)
210,545✔
2135
            {
2136
                // skip non-top-level dominions anyway
2137
                // (will check it later and downgrade if necessary according to amount of material (shards))
2138
                /*if (!card->is_top_level_card())
2139
                  { continue; }
2140

2141
                // skip basic dominions
2142
                if ((card->m_id == 50001) || (card->m_id == 50002))
2143
                { continue; }*/
2144
            }
2145

2146
            // handle normal cards
2147
            else
2148
            {
2149
                // skip non-top-level cards (adjust_deck() will try to downgrade them if necessary)
2150
                bool use_top_level = (card->m_type == CardType::commander) ? use_top_level_commander : use_top_level_card;
208,755✔
2151
                if (!card->is_top_level_card() and (fund || use_top_level || !owned_cards[card->m_id]))
208,755✔
2152
                {
2153
                    continue;
173,440✔
2154
                }
2155

2156
                // skip lowest fusion levels
2157
                unsigned use_fused_level = (card->m_type == CardType::commander) ? use_fused_commander_level : use_fused_card_level;
35,315✔
2158
                if (card->m_fusion_level < use_fused_level)
35,315✔
2159
                {
2160
                    continue;
×
2161
                }
2162
            }
2163
        }
2164

2165
        if (use_owned_cards && card->m_category == CardCategory::dominion_alpha && !owned_cards[card->m_id])
37,105✔
2166
        {
2167
            if (use_maxed_dominions && card->m_used_for_cards.size() == 0)
1,775✔
2168
            {
2169
            }
2170
            else
2171
            {
2172
                continue;
1,775✔
2173
            }
2174
        }
2175
        // skip unavailable cards anyway when ownedcards is used
2176
        if (use_owned_cards && !(card->m_category == CardCategory::dominion_alpha) && !is_owned_or_can_be_fused(card))
35,330✔
2177
        {
2178
            continue;
35,155✔
2179
        }
2180
        // mono
2181
        if (!factions.empty() && (std::find(factions.begin(), factions.end(), card->m_faction) == factions.end()) != invert_factions) // XOR
175✔
2182
        {
2183
            continue;
×
2184
        }
2185

2186
        // enqueue candidate according to category & type
2187
        if (card->m_type == CardType::commander)
175✔
2188
        {
2189
            commander_candidates.emplace_back(card);
45✔
2190
        }
2191
        else if (card->m_category == CardCategory::dominion_alpha)
130✔
2192
        {
2193
            alpha_dominion_candidates.emplace_back(card);
15✔
2194
        }
2195
        else if (card->m_category == CardCategory::normal)
115✔
2196
        {
2197
            bool contains{false};
115✔
2198
            if (!skills.empty())
115✔
2199
            {
2200
                for (Skill::Skill skill_id : skills)
×
2201
                {
2202
                    if (card->m_skill_value[skill_id])
×
2203
                    {
2204
                        contains = true;
2205
                        break;
2206
                    }
2207
                }
2208
                if (!contains != invert_skills)
×
2209
                    continue; // XOR
×
2210
            }
2211
            if (only_recent && !((it + (player_assaults_and_structures.size() * recent_percent / 100)) > player_assaults_and_structures.end()))
115✔
2212
                continue;                                                                                                                      // latest %
×
2213
            if (prefered_recent && it + (player_assaults_and_structures.size() * recent_percent) / 100 > player_assaults_and_structures.end()) // latest %
115✔
2214
            {
2215
                for (unsigned k = 0; k < prefered_factor; ++k)
×
2216
                    card_candidates.emplace_back(card);
×
2217
            }
2218
            for (Skill::Skill skill_id : prefered_skills)
115✔
2219
            {
2220
                if (card->m_skill_value[skill_id])
×
2221
                {
2222
                    for (unsigned k = 0; k < prefered_factor; ++k)
×
2223
                        card_candidates.emplace_back(card);
×
2224
                }
2225
            }
2226

2227
            card_candidates.emplace_back(card);
115✔
2228
        }
2229
    }
2230
    card_candidates.emplace_back(nullptr);
5✔
2231

2232
    ret_candidates.emplace_back(commander_candidates);
5✔
2233
    ret_candidates.emplace_back(alpha_dominion_candidates);
5✔
2234
    ret_candidates.emplace_back(card_candidates);
5✔
2235
    return ret_candidates;
5✔
2236
}
5✔
2237
//------------------------------------------------------------------------------
2238
enum Operation
2239
{
2240
    noop,
2241
    simulate,
2242
    climb,
2243
    climb_forts,
2244
    anneal,
2245
    genetic,
2246
    beam,
2247
    reorder,
2248
    debug,
2249
    debuguntil,
2250
};
2251
//------------------------------------------------------------------------------
2252
extern void (*skill_table[Skill::num_skills])(Field *, CardStatus *src_status, const SkillSpec &);
2253
void print_available_effects()
×
2254
{
2255
    std::cout << "Available effects besides activation skills:\n"
×
2256
                 "  Bloodlust X\n"
2257
                 "  Brigade\n"
2258
                 "  Counterflux\n"
2259
                 "  Divert\n"
2260
                 "  Enduring-Rage\n"
2261
                 "  Fortification\n"
2262
                 "  Heroism\n"
2263
                 "  Zealots-Preservation\n"
2264
                 "  Metamorphosis\n"
2265
                 "  Megamorphosis\n"
2266
                 "  Revenge X\n"
2267
                 "  Turning-Tides\n"
2268
                 "  Virulence\n"
2269
                 "  Halted-Orders\n"
2270
                 "  Devour X\n"
2271
                 "  Critical-Reach\n"
2272
                 "  Temporal-Backlash\n"
2273
                 "  Furiosity\n"
2274
                 "  Oath-Of-Loyalty\n"
2275
                 "  Blood-Vengeance\n"
2276
                 "  Cold-Sleep\n"
2277
                 "  Iron-Will\n"
2278
                 "  Unity\n"
2279
                 "  Devotion\n"
2280
                 "  Crackdown\n"
2281
                 "  SuperHeroism\n";
×
2282
}
×
2283
bool check_input_amount(int argc, const char **argv, int argIndex, int number)
384✔
2284
{
2285
    if (argc <= argIndex + number)
384✔
2286
    {
2287
        std::cerr << argv[argIndex] << " needs " << number << " paramters" << std::endl;
×
2288
        return true;
×
2289
    }
2290
    return false;
2291
}
2292
void input_error(std::string msg)
×
2293
{
2294
    std::cerr << msg << std::endl;
×
2295
    exit(EXIT_FAILURE);
×
2296
}
2297
void usage(int argc, const char **argv)
×
2298
{
2299
    std::cout << "Tyrant Unleashed Optimizer (TUO) " << TYRANT_OPTIMIZER_VERSION << "\n"
×
2300
                                                                                    "usage: "
2301
              << argv[0] << " Your_Deck Enemy_Deck [Flags] [Operations]\n"
2302
                            "\n"
2303
                            "Your_Deck:\n"
2304
                            "  the name/hash/cards of a custom deck.\n"
2305
                            "\n"
2306
                            "Enemy_Deck:\n"
2307
                            "  semicolon separated list of defense decks, syntax:\n"
2308
                            "  deck1[:factor1];deck2[:factor2];...\n"
2309
                            "  where deck is the name/hash/cards of a mission, raid, quest or custom deck, and factor is optional. The default factor is 1.\n"
2310
                            "  example: \'fear:0.2;slowroll:0.8\' means fear is the defense deck 20% of the time, while slowroll is the defense deck 80% of the time.\n"
2311
                            "\n"
2312
                            "Flags:\n"
2313
                            "  -e \"<effect>\": set the battleground effect; you may use -e multiple times.\n"
2314
                            "  -r: the attack deck is played in order instead of randomly (respects the 3 cards drawn limit).\n"
2315
                            "  -s: use surge (default is fight).\n"
2316
                            "  -t <num>: set the number of threads, default is 4.\n"
2317
                            "  win:     simulate/optimize for win rate. default for non-raids.\n"
2318
                            "  defense: simulate/optimize for win rate + stall rate. can be used for defending deck or win rate oriented raid simulations.\n"
2319
                            "  raid:    simulate/optimize for average raid damage (ARD). default for raids.\n"
2320
                            "Flags for climb:\n"
2321
                            "  -c: don't try to optimize the commander.\n"
2322
                            "  -L <min> <max>: restrict deck size between <min> and <max>.\n"
2323
                            "  -o: restrict to the owned cards listed in \"data/ownedcards.txt\".\n"
2324
                            "  -o=<filename>: restrict to the owned cards listed in <filename>.\n"
2325
                            "  fund <num>: invest <num> SP to upgrade cards.\n"
2326
                            "  target <num>: stop as soon as the score reaches <num>.\n"
2327
                            "\n"
2328
                            "Operations:\n"
2329
                            "  sim <num>: simulate <num> battles to evaluate a deck.\n"
2330
                            "  climb <num>: perform hill-climbing starting from the given attack deck, using up to <num> battles to evaluate a deck.\n"
2331
                            "  reorder <num>: optimize the order for given attack deck, using up to <num> battles to evaluate an order.\n"
2332
#ifndef NDEBUG
2333
                            "  debug: testing purpose only. very verbose output. only one battle.\n"
2334
                            "  debuguntil <min> <max>: testing purpose only. fight until the last fight results in range [<min>, <max>]. recommend to redirect std::coutput.\n"
×
2335
#endif
2336
        ;
2337
}
×
2338

2339
bool parse_bge(
61✔
2340
    std::string bge_name,
2341
    unsigned player,
2342
    const std::unordered_map<std::string, std::string> &bge_aliases,
2343
    std::array<signed short, PassiveBGE::num_passive_bges> &your_bg_effects,
2344
    std::array<signed short, PassiveBGE::num_passive_bges> &enemy_bg_effects,
2345
    std::vector<SkillSpec> &your_bg_skills,
2346
    std::vector<SkillSpec> &enemy_bg_skills,
2347
    std::unordered_set<std::string> used_bge_aliases)
2348
{
2349
    // skip empty
2350
    tuo::trim(bge_name);
61✔
2351
    if (bge_name.empty())
61✔
2352
    {
2353
        return true;
2354
    }
2355

2356
    // is effect combined?
2357
    if (bge_name.find_first_of(";|") != std::string::npos)
36✔
2358
    {
2359
        std::vector<std::string> bges;
×
2360
        boost::split(bges, bge_name, boost::is_any_of(";|"));
×
2361
        for (auto &&next_bge : bges)
×
2362
        {
2363
            if (!parse_bge(next_bge, player, bge_aliases, your_bg_effects, enemy_bg_effects, your_bg_skills, enemy_bg_skills, used_bge_aliases))
×
2364
            {
2365
                return false;
×
2366
            }
2367
        }
2368
        return true;
2369
    }
×
2370

2371
    // try to resolve bge name as alias
2372
    std::string simple_bge_name = simplify_name(bge_name);
36✔
2373
    const auto bge_alias_itr = bge_aliases.find(simple_bge_name);
36✔
2374
    if (bge_alias_itr != bge_aliases.end())
36✔
2375
    {
2376
        if (!used_bge_aliases.insert(simple_bge_name).second)
1✔
2377
        {
2378
            throw std::runtime_error("BGE alias: " + bge_name + ": Circular reference");
×
2379
        }
2380
        return parse_bge(bge_alias_itr->second, player, bge_aliases, your_bg_effects, enemy_bg_effects, your_bg_skills, enemy_bg_skills, used_bge_aliases);
3✔
2381
    }
2382

2383
    // parse as passive or skill based BGE
2384
    std::vector<std::string> tokens, skill_name_list;
35✔
2385
    boost::split(tokens, bge_name, boost::is_any_of(" "));
35✔
2386
    boost::split(skill_name_list, tokens[0], boost::is_any_of("+"));
35✔
2387
    try
35✔
2388
    {
2389
        for (auto &&skill_name : skill_name_list)
70✔
2390
        {
2391
            PassiveBGE::PassiveBGE passive_bge_id = passive_bge_name_to_id(skill_name);
35✔
2392
            Skill::Skill skill_id = skill_name_to_id(skill_name);
35✔
2393
            if (passive_bge_id != PassiveBGE::no_bge)
35✔
2394
            {
2395
                // map bge id to its value (if present otherwise zero)
2396
                signed short bge_value = (tokens.size() > 1) ? boost::lexical_cast<signed short>(tokens[1]) : 1;
34✔
2397
                if (!bge_value)
6✔
2398
                    throw std::runtime_error("BGE " + skill_name + ": zero value means no BGE");
×
2399
                if ((player == 0) or (player == 2))
34✔
2400
                    your_bg_effects[passive_bge_id] = bge_value;
34✔
2401
                if ((player == 1) or (player == 2))
34✔
2402
                    enemy_bg_effects[passive_bge_id] = bge_value;
34✔
2403
            }
2404
            else if (skill_table[skill_id] != nullptr)
1✔
2405
            {
2406
                unsigned skill_index = 1;
1✔
2407
                // activation BG skill
2408
                SkillSpec bg_skill{
1✔
2409
                    skill_id,
2410
                    0,
2411
                    allfactions,
2412
                    0,
2413
                    0,
2414
                    Skill::no_skill,
2415
                    Skill::no_skill,
2416
                    false,
2417
                    0,
2418
                };
1✔
2419

2420
                // skill [ ALL | N ] ...
2421
                if (skill_index < tokens.size() && boost::to_lower_copy(tokens[skill_index]) == "all")
2✔
2422
                {
2423
                    bg_skill.all = true;
1✔
2424
                    skill_index += 1;
1✔
2425
                }
2426
                else if (skill_index + 1 < tokens.size() && isdigit(*tokens[skill_index].c_str()))
×
2427
                {
2428
                    bg_skill.n = boost::lexical_cast<unsigned>(tokens[skill_index]);
×
2429
                    skill_index += 1;
×
2430
                }
2431

2432
                // skill n [ FACTION ] ...
2433
                if ((skill_index + 1) < tokens.size())
1✔
2434
                {
2435
                    for (auto f = allfactions + 1; f < num_factions; ++f)
7✔
2436
                    {
2437
                        if (boost::to_lower_copy(tokens[skill_index]) == faction_names[f])
6✔
2438
                        {
2439
                            bg_skill.y = static_cast<Faction>(f);
×
2440
                            skill_index += 1;
×
2441
                            break;
×
2442
                        }
2443
                    }
2444
                }
2445

2446
                if (skill_index < tokens.size())
1✔
2447
                {
2448
                    bg_skill.s = skill_name_to_id(tokens[skill_index]);
1✔
2449
                    if (bg_skill.s != Skill::no_skill)
1✔
2450
                    {
2451
                        skill_index += 1;
1✔
2452
                        if (skill_index < tokens.size() && (boost::to_lower_copy(tokens[skill_index]) == "to" || boost::to_lower_copy(tokens[skill_index]) == "into"))
3✔
2453
                        {
2454
                            skill_index += 1;
×
2455
                        }
2456
                        if (skill_index < tokens.size())
1✔
2457
                        {
2458
                            bg_skill.s2 = skill_name_to_id(tokens[skill_index]);
1✔
2459
                            if (bg_skill.s2 != Skill::no_skill)
1✔
2460
                            {
2461
                                skill_index += 1;
×
2462
                            }
2463
                        }
2464
                    }
2465
                }
2466
                if (skill_index < tokens.size())
1✔
2467
                {
2468
                    if (bg_skill.id == Skill::jam || bg_skill.id == Skill::overload)
1✔
2469
                    {
2470
                        bg_skill.n = boost::lexical_cast<unsigned>(tokens[skill_index]);
×
2471
                    }
2472
                    else
2473
                    {
2474
                        bg_skill.x = boost::lexical_cast<unsigned>(tokens[skill_index]);
1✔
2475
                    }
2476
                }
2477
                switch (player)
1✔
2478
                {
2479
                case 0:
×
2480
                    your_bg_skills.push_back(bg_skill);
×
2481
                    break;
2482
                case 1:
×
2483
                    enemy_bg_skills.push_back(bg_skill);
×
2484
                    break;
2485
                case 2:
1✔
2486
                    your_bg_skills.push_back(bg_skill);
1✔
2487
                    enemy_bg_skills.push_back(bg_skill);
1✔
2488
                    break;
2489
                default:
×
2490
                    throw std::runtime_error("Bad player number: " + boost::lexical_cast<std::string>(player));
×
2491
                }
2492
            }
2493
            else
2494
            {
2495
                std::cerr << "Error: unrecognized effect \"" << bge_name << "\".\n";
×
2496
                std::cout << "Unrecognized effect \"" << bge_name << "\".\n";
×
2497
                print_available_effects();
×
2498
                return false;
35✔
2499
            }
2500
        }
2501
    }
2502
    catch (const boost::bad_lexical_cast &e)
×
2503
    {
2504
        throw std::runtime_error("Expect a number in effect \"" + bge_name + "\"");
×
2505
    }
×
2506
    return true;
2507
}
71✔
2508

2509
DeckResults run(int argc, const char **argv)
81✔
2510
{
2511
    start_time = std::chrono::system_clock::now();
81✔
2512
    DeckResults fr;
81✔
2513
    opt_num_threads = 4;
81✔
2514
    DeckStrategy::DeckStrategy opt_your_strategy(DeckStrategy::random);
81✔
2515
    DeckStrategy::DeckStrategy opt_enemy_strategy(DeckStrategy::random);
81✔
2516
    std::string opt_forts, opt_enemy_forts;
81✔
2517
    std::string opt_doms, opt_enemy_doms;
81✔
2518
    std::string opt_hand, opt_enemy_hand;
81✔
2519
    std::string opt_vip;
81✔
2520
    std::string opt_allow_candidates;
81✔
2521
    std::string opt_disallow_candidates;
81✔
2522
    std::string opt_disallow_recipes;
81✔
2523
#ifndef NQUEST
2524
    std::string opt_quest;
2525
#endif
2526
    std::string opt_target_score;
81✔
2527
    std::vector<std::string> fn_suffix_list{
81✔
2528
        "",
2529
    };
162✔
2530
    std::string prefix = "";
162✔
2531
    std::vector<std::string> opt_owned_cards_str_list;
81✔
2532
    bool opt_do_optimization(false);
81✔
2533
    bool opt_multi_optimization(false);
81✔
2534
    bool opt_do_reorder(false);
81✔
2535
    bool opt_keep_commander{false};
81✔
2536
    std::vector<std::tuple<unsigned, unsigned, Operation>> opt_todo;
81✔
2537
    std::vector<std::string> opt_effects[3]; // 0-you; 1-enemy; 2-global
567✔
2538
    std::array<signed short, PassiveBGE::num_passive_bges> opt_bg_effects[2];
2539
    std::vector<SkillSpec> opt_bg_skills[2];
405✔
2540

2541
    std::string your_deck_list{argv[1]};
81✔
2542
    std::string enemy_deck_list{argv[2]};
81✔
2543

2544
    for (int argIndex = 3; argIndex < argc; ++argIndex)
488✔
2545
    {
2546
        //bypass MSVS issue: Nesting of code blocks exceeds the limit of 128 nesting levels
2547
        bool tokenParsed = true;
407✔
2548
        if (strcmp(argv[argIndex], "deck") == 0)
407✔
2549
        {
2550
            your_deck_list = std::string(argv[argIndex + 1]);
×
2551
            argIndex += 1;
×
2552
        }
2553
        else if (strcmp(argv[argIndex], "enemy:deck") == 0)
407✔
2554
        {
2555
            enemy_deck_list = std::string(argv[argIndex + 1]);
×
2556
            argIndex += 1;
×
2557
        }
2558
        // Codec
2559
        else if (strcmp(argv[argIndex], "ext_b64") == 0)
407✔
2560
        {
2561
            hash_to_ids = hash_to_ids_ext_b64;
×
2562
            encode_deck = encode_deck_ext_b64;
×
2563
        }
2564
        else if (strcmp(argv[argIndex], "wmt_b64") == 0)
407✔
2565
        {
2566
            hash_to_ids = hash_to_ids_wmt_b64;
×
2567
            encode_deck = encode_deck_wmt_b64;
×
2568
        }
2569
        else if (strcmp(argv[argIndex], "ddd_b64") == 0)
407✔
2570
        {
2571
            hash_to_ids = hash_to_ids_ddd_b64;
×
2572
            encode_deck = encode_deck_ddd_b64;
×
2573
        }
2574
        else if (strcmp(argv[argIndex], "ml") == 0)
407✔
2575
        {
2576
            use_ml = true;
1✔
2577
            use_db_write = false;
1✔
2578
        }
2579
        else if (strcmp(argv[argIndex], "noop") == 0)
406✔
2580
        {
2581
        }
2582
        else if (strcmp(argv[argIndex], "no-ml") == 0)
406✔
2583
        {
2584
            use_ml = false;
×
2585
        }
2586
        else if (strcmp(argv[argIndex], "only-ml") == 0)
406✔
2587
        {
2588
            use_ml = true;
×
2589
            use_only_ml = true;
×
2590
            use_db_write = false;
×
2591
        }
2592
        else if (strcmp(argv[argIndex], "ml-precision") == 0)
406✔
2593
        {
2594
            if (check_input_amount(argc, argv, argIndex, 1))
×
2595
                exit(1);
×
2596
            ml_precision = std::stof(argv[argIndex + 1]);
×
2597
            argIndex += 1;
×
2598
        }
2599
        else if (strcmp(argv[argIndex], "no-db-write") == 0)
406✔
2600
        {
2601
            use_db_write = false;
×
2602
        }
2603
        else if (strcmp(argv[argIndex], "no-db-load") == 0)
406✔
2604
        {
2605
            use_db_load = false;
2✔
2606
        }
2607
        else if (strcmp(argv[argIndex], "no-db") == 0)
404✔
2608
        {
2609
            use_db_load = false;
16✔
2610
            use_db_write = false;
16✔
2611
        }
2612
        else if (strcmp(argv[argIndex], "db") == 0)
388✔
2613
        {
2614
            use_db_load = true;
2✔
2615
            use_db_write = true;
2✔
2616
        }
2617
        else if (strcmp(argv[argIndex], "strict-db") == 0)
386✔
2618
        {
2619
            use_strict_db = true;
×
2620
            use_db_load = true;
×
2621
            use_db_write = true;
×
2622
        }
2623
        else if (strcmp(argv[argIndex], "no-strict-db") == 0)
386✔
2624
        {
2625
            use_strict_db = false;
×
2626
        }
2627
        else if (strcmp(argv[argIndex], "db-limit") == 0)
386✔
2628
        {
2629
            if (check_input_amount(argc, argv, argIndex, 1))
×
2630
                exit(1);
×
2631
            db_limit = std::stoi(argv[argIndex + 1]);
×
2632
            argIndex += 1;
×
2633
        }
2634

2635
        // Base Game Mode
2636
        else if (strcmp(argv[argIndex], "fight") == 0)
386✔
2637
        {
2638
            gamemode = fight;
×
2639
        }
2640
        else if (strcmp(argv[argIndex], "-s") == 0 || strcmp(argv[argIndex], "surge") == 0)
386✔
2641
        {
2642
            gamemode = surge;
×
2643
        }
2644
        // Base Scoring Mode
2645
        else if (strcmp(argv[argIndex], "win") == 0)
386✔
2646
        {
2647
            optimization_mode = OptimizationMode::winrate;
×
2648
        }
2649
        else if (strcmp(argv[argIndex], "defense") == 0)
386✔
2650
        {
2651
            optimization_mode = OptimizationMode::defense;
×
2652
        }
2653
        else if (strcmp(argv[argIndex], "raid") == 0)
386✔
2654
        {
2655
            optimization_mode = OptimizationMode::raid;
×
2656
        }
2657
        // Mode Package
2658
        else if (strcmp(argv[argIndex], "campaign") == 0)
386✔
2659
        {
2660
            gamemode = surge;
×
2661
            optimization_mode = OptimizationMode::campaign;
×
2662
        }
2663
        else if (strcmp(argv[argIndex], "pvp") == 0)
386✔
2664
        {
2665
            gamemode = fight;
×
2666
            optimization_mode = OptimizationMode::winrate;
×
2667
        }
2668
        else if (strcmp(argv[argIndex], "pvp-defense") == 0)
386✔
2669
        {
2670
            gamemode = surge;
×
2671
            optimization_mode = OptimizationMode::defense;
×
2672
        }
2673
        else if (strcmp(argv[argIndex], "brawl") == 0)
386✔
2674
        {
2675
            gamemode = surge;
×
2676
            optimization_mode = OptimizationMode::brawl;
×
2677
        }
2678
        else if (strcmp(argv[argIndex], "brawl-defense") == 0)
386✔
2679
        {
2680
            gamemode = fight;
×
2681
            optimization_mode = OptimizationMode::brawl_defense;
×
2682
        }
2683
        else if (strcmp(argv[argIndex], "gw") == 0)
386✔
2684
        {
2685
            gamemode = surge;
×
2686
            optimization_mode = OptimizationMode::war;
×
2687
        }
2688
        else if (strcmp(argv[argIndex], "gw-defense") == 0)
386✔
2689
        {
2690
            gamemode = fight;
×
2691
            optimization_mode = OptimizationMode::war_defense;
×
2692
        }
2693
        // Others
2694
        else if (strcmp(argv[argIndex], "keep-commander") == 0 || strcmp(argv[argIndex], "-c") == 0)
386✔
2695
        {
2696
            opt_keep_commander = true;
2697
        }
2698
        else if (strcmp(argv[argIndex], "mono") == 0 || strcmp(argv[argIndex], "-m") == 0 || strcmp(argv[argIndex], "factions") == 0 || strcmp(argv[argIndex], "-f") == 0)
386✔
2699
        {
2700
            if (check_input_amount(argc, argv, argIndex, 1))
×
2701
                exit(1);
×
2702
            if (strcmp(argv[argIndex + 1], "") != 0)
×
2703
            {
2704
                factions.push_back(faction_name_to_id(argv[argIndex + 1]));
×
2705
            }
2706
            argIndex += 1;
×
2707
        }
2708
        else if (strcmp(argv[argIndex], "no-mono") == 0 || strcmp(argv[argIndex], "no-factions") == 0)
386✔
2709
        {
2710
            if (check_input_amount(argc, argv, argIndex, 1))
×
2711
                exit(1);
×
2712
            factions.push_back(faction_name_to_id(argv[argIndex + 1]));
×
2713
            invert_factions = true;
×
2714
            argIndex += 1;
×
2715
        }
2716
        else if (strcmp(argv[argIndex], "strategy") == 0 || strcmp(argv[argIndex], "skill") == 0)
386✔
2717
        {
2718
            if (check_input_amount(argc, argv, argIndex, 1))
×
2719
                exit(1);
×
2720
            if (strcmp(argv[argIndex + 1], "") != 0)
×
2721
            {
2722
                if (strcmp(argv[argIndex + 1], "recent") == 0)
×
2723
                {
2724
                    only_recent = true;
×
2725
                }
2726
                else
2727
                {
2728
                    skills.push_back(skill_name_to_id(argv[argIndex + 1]));
×
2729
                }
2730
            }
2731
            argIndex += 1;
×
2732
        }
2733
        else if (strcmp(argv[argIndex], "no-strategy") == 0 || strcmp(argv[argIndex], "no-skill") == 0)
386✔
2734
        {
2735
            if (check_input_amount(argc, argv, argIndex, 1))
×
2736
                exit(1);
×
2737
            if (strcmp(argv[argIndex + 1], "") != 0)
×
2738
            {
2739
                skills.push_back(skill_name_to_id(argv[argIndex + 1]));
×
2740
                invert_skills = true;
×
2741
            }
2742
            argIndex += 1;
×
2743
        }
2744
        else if (strcmp(argv[argIndex], "prefered-strategy") == 0 || strcmp(argv[argIndex], "prefered-skill") == 0)
386✔
2745
        {
2746
            if (check_input_amount(argc, argv, argIndex, 1))
×
2747
                exit(1);
×
2748
            if (strcmp(argv[argIndex + 1], "") != 0)
×
2749
            {
2750
                if (strcmp(argv[argIndex + 1], "recent") == 0)
×
2751
                {
2752
                    prefered_recent = true;
×
2753
                }
2754
                else
2755
                {
2756
                    prefered_skills.push_back(skill_name_to_id(argv[argIndex + 1]));
×
2757
                }
2758
            }
2759
            argIndex += 1;
×
2760
        }
2761
        else if (strcmp(argv[argIndex], "prefered-factor") == 0)
386✔
2762
        {
2763
            if (check_input_amount(argc, argv, argIndex, 1))
×
2764
                exit(1);
×
2765
            prefered_factor = std::stoi(argv[argIndex + 1]);
×
2766
            argIndex += 1;
×
2767
        }
2768
        else if (strcmp(argv[argIndex], "recent-percent") == 0)
386✔
2769
        {
2770
            if (check_input_amount(argc, argv, argIndex, 1))
×
2771
                exit(1);
×
2772
            recent_percent = std::stoi(argv[argIndex + 1]);
×
2773
            argIndex += 1;
×
2774
        }
2775
        else if (strcmp(argv[argIndex], "effect") == 0 || strcmp(argv[argIndex], "-e") == 0)
386✔
2776
        {
2777
            if (check_input_amount(argc, argv, argIndex, 1))
60✔
2778
                exit(1);
×
2779
            opt_effects[2].push_back(argv[argIndex + 1]);
120✔
2780
            argIndex += 1;
60✔
2781
        }
2782
        else if (strcmp(argv[argIndex], "ye") == 0 || strcmp(argv[argIndex], "yeffect") == 0)
326✔
2783
        {
2784
            if (check_input_amount(argc, argv, argIndex, 1))
×
2785
                exit(1);
×
2786
            opt_effects[0].push_back(argv[argIndex + 1]);
×
2787
            argIndex += 1;
×
2788
        }
2789
        else if (strcmp(argv[argIndex], "ee") == 0 || strcmp(argv[argIndex], "eeffect") == 0)
326✔
2790
        {
2791
            if (check_input_amount(argc, argv, argIndex, 1))
×
2792
                exit(1);
×
2793
            opt_effects[1].push_back(argv[argIndex + 1]);
×
2794
            argIndex += 1;
×
2795
        }
2796
        else if (strcmp(argv[argIndex], "freeze") == 0 || strcmp(argv[argIndex], "-F") == 0)
326✔
2797
        {
2798
            if (check_input_amount(argc, argv, argIndex, 1))
×
2799
                exit(1);
×
2800
            opt_freezed_cards = atoi(argv[argIndex + 1]);
×
2801
            argIndex += 1;
×
2802
        }
2803
        else if (strcmp(argv[argIndex], "-L") == 0)
326✔
2804
        {
2805
            if (check_input_amount(argc, argv, argIndex, 2))
×
2806
                exit(1);
×
2807
            min_deck_len = atoi(argv[argIndex + 1]);
×
2808
            max_deck_len = atoi(argv[argIndex + 2]);
×
2809
            argIndex += 2;
×
2810
        }
2811
        else if (strcmp(argv[argIndex], "-o-") == 0)
326✔
2812
        {
2813
            use_owned_cards = false;
×
2814
        }
2815
        else if (strcmp(argv[argIndex], "-o") == 0)
326✔
2816
        {
2817
            opt_owned_cards_str_list.push_back(prefix + "data/ownedcards.txt");
×
2818
            use_owned_cards = true;
×
2819
        }
2820
        else if (strncmp(argv[argIndex], "-o=", 3) == 0)
326✔
2821
        {
2822
            std::string fname{prefix};
×
2823
            fname += (argv[argIndex] + 3);
×
2824
            opt_owned_cards_str_list.push_back(fname);
×
2825
            use_owned_cards = true;
×
2826
        }
×
2827
        else if (strncmp(argv[argIndex], "_", 1) == 0)
326✔
2828
        {
2829
            fn_suffix_list.push_back(argv[argIndex]);
×
2830
        }
2831
        else if (strcmp(argv[argIndex], "prefix") == 0)
326✔
2832
        {
2833
            if (check_input_amount(argc, argv, argIndex, 1))
81✔
2834
                exit(1);
×
2835
            prefix = argv[argIndex + 1];
81✔
2836
            argIndex += 1;
81✔
2837
        }
2838
        else if (strcmp(argv[argIndex], "fund") == 0)
245✔
2839
        {
2840
            if (check_input_amount(argc, argv, argIndex, 1))
×
2841
                exit(1);
×
2842
            fund = atoi(argv[argIndex + 1]);
×
2843
            argIndex += 1;
×
2844
        }
2845
        else if (strcmp(argv[argIndex], "dom-none") == 0)
245✔
2846
        {
2847
            use_owned_dominions = false;
×
2848
            use_maxed_dominions = false;
×
2849
        }
2850
        else if (strcmp(argv[argIndex], "dom+") == 0 || strcmp(argv[argIndex], "dominion+") == 0 || strcmp(argv[argIndex], "dom-owned") == 0)
245✔
2851
        {
2852
            use_owned_dominions = true;
×
2853
            use_maxed_dominions = false;
×
2854
        }
2855
        else if (strcmp(argv[argIndex], "dom-") == 0 || strcmp(argv[argIndex], "dominion-") == 0 || strcmp(argv[argIndex], "dom-maxed") == 0)
245✔
2856
        {
2857
            use_owned_dominions = true;
×
2858
            use_maxed_dominions = true;
×
2859
        }
2860
        else if (strcmp(argv[argIndex], "random") == 0)
245✔
2861
        {
2862
            opt_your_strategy = DeckStrategy::random;
2863
        }
2864
        else if (strcmp(argv[argIndex], "-r") == 0 || strcmp(argv[argIndex], "ordered") == 0)
245✔
2865
        {
2866
            opt_your_strategy = DeckStrategy::ordered;
2867
        }
2868
        else if (strcmp(argv[argIndex], "flexible") == 0 || strcmp(argv[argIndex], "flex") == 0)
245✔
2869
        {
2870
            opt_your_strategy = DeckStrategy::flexible;
2871
        }
2872
        else if (strcmp(argv[argIndex], "flexible-iter") == 0)
245✔
2873
        {
2874
            if (check_input_amount(argc, argv, argIndex, 1))
×
2875
                exit(1);
×
2876
            flexible_iter = atoi(argv[argIndex + 1]);
×
2877
            argIndex += 1;
×
2878
        }
2879
        else if (strcmp(argv[argIndex], "flexible-turn") == 0)
245✔
2880
        {
2881
            if (check_input_amount(argc, argv, argIndex, 1))
×
2882
                exit(1);
×
2883
            flexible_turn = atoi(argv[argIndex + 1]);
×
2884
            argIndex += 1;
×
2885
        }
2886
        else if (strcmp(argv[argIndex], "evaluate") == 0 || strcmp(argv[argIndex], "eval") == 0)
245✔
2887
        {
2888
            opt_your_strategy = DeckStrategy::evaluate;
2889
        }
2890
        else if (strcmp(argv[argIndex], "evaluate2") == 0 || strcmp(argv[argIndex], "eval2") == 0)
245✔
2891
        {
2892
            opt_your_strategy = DeckStrategy::evaluate_twice;
2893
        }
2894
        else if (strcmp(argv[argIndex], "eval-iter") == 0)
245✔
2895
        {
2896
            if (check_input_amount(argc, argv, argIndex, 1))
×
2897
                exit(1);
×
2898
            eval_iter = atoi(argv[argIndex + 1]);
×
2899
            argIndex += 1;
×
2900
        }
2901
        else if (strcmp(argv[argIndex], "eval-turn") == 0)
245✔
2902
        {
2903
            if (check_input_amount(argc, argv, argIndex, 1))
×
2904
                exit(1);
×
2905
            eval_turn = atoi(argv[argIndex + 1]);
×
2906
            argIndex += 1;
×
2907
        }
2908
        else if (strcmp(argv[argIndex], "exact-ordered") == 0)
245✔
2909
        {
2910
            opt_your_strategy = DeckStrategy::exact_ordered;
2911
        }
2912
        else if (strcmp(argv[argIndex], "enemy:evaluate") == 0)
245✔
2913
        {
2914
            opt_enemy_strategy = DeckStrategy::evaluate;
2915
        }
2916
        else if (strcmp(argv[argIndex], "enemy:evaluate2") == 0)
245✔
2917
        {
2918
            opt_enemy_strategy = DeckStrategy::evaluate_twice;
2919
        }
2920
        else if (strcmp(argv[argIndex], "enemy:flexible") == 0)
245✔
2921
        {
2922
            opt_enemy_strategy = DeckStrategy::flexible;
2923
        }
2924
        else if (strcmp(argv[argIndex], "enemy:ordered") == 0)
245✔
2925
        {
2926
            opt_enemy_strategy = DeckStrategy::ordered;
2927
        }
2928
        else if (strcmp(argv[argIndex], "enemy:flexible") == 0)
245✔
2929
        {
2930
            opt_enemy_strategy = DeckStrategy::flexible;
2931
        }
2932
        else if (strcmp(argv[argIndex], "enemy:exact-ordered") == 0)
245✔
2933
        {
2934
            opt_enemy_strategy = DeckStrategy::exact_ordered;
2935
        }
2936
        else if (strcmp(argv[argIndex], "endgame") == 0)
245✔
2937
        {
2938
            if (check_input_amount(argc, argv, argIndex, 1))
×
2939
                exit(1);
×
2940
            use_fused_card_level = atoi(argv[argIndex + 1]);
×
2941
            argIndex += 1;
×
2942
        }
2943
#ifndef NQUEST
2944
        else if (strcmp(argv[argIndex], "quest") == 0)
2945
        {
2946
            if (check_input_amount(argc, argv, argIndex, 1))
2947
                exit(1);
2948
            opt_quest = argv[argIndex + 1];
2949
            argIndex += 1;
2950
        }
2951
#endif
2952
        else if (strcmp(argv[argIndex], "threads") == 0 || strcmp(argv[argIndex], "-t") == 0)
245✔
2953
        {
2954
            if (check_input_amount(argc, argv, argIndex, 1))
81✔
2955
                exit(1);
×
2956
            opt_num_threads = atoi(argv[argIndex + 1]);
81✔
2957
#ifdef _OPENMP
2958
            omp_set_num_threads(opt_num_threads);
2959
#endif
2960
            argIndex += 1;
81✔
2961
        }
2962
        else if (strcmp(argv[argIndex], "target") == 0)
164✔
2963
        {
2964
            if (check_input_amount(argc, argv, argIndex, 1))
×
2965
                exit(1);
×
2966
            opt_target_score = argv[argIndex + 1];
×
2967
            argIndex += 1;
×
2968
        }
2969
        else if (strcmp(argv[argIndex], "turnlimit") == 0)
164✔
2970
        {
2971
            if (check_input_amount(argc, argv, argIndex, 1))
×
2972
                exit(1);
×
2973
            turn_limit = atoi(argv[argIndex + 1]);
×
2974
            argIndex += 1;
×
2975
        }
2976
        else if (strcmp(argv[argIndex], "mis") == 0)
164✔
2977
        {
2978
            if (check_input_amount(argc, argv, argIndex, 1))
×
2979
                exit(1);
×
2980
            min_increment_of_score = atof(argv[argIndex + 1]);
×
2981
            argIndex += 1;
×
2982
        }
2983
        else if (strcmp(argv[argIndex], "timeout") == 0) // set timeout in hours. tuo will stop approx. at the given time.
164✔
2984
        {
2985
            if (check_input_amount(argc, argv, argIndex, 1))
×
2986
                exit(1);
×
2987
            maximum_time = atof(argv[argIndex + 1]);
×
2988
            argIndex += 1;
×
2989
        }
2990
        else if (strcmp(argv[argIndex], "strict-ownedcards") == 0) // won't add missing cards, instead skips faulty decks
164✔
2991
        {
2992
            opt_skip_unclaimed_decks = true;
×
2993
        }
2994
        /*else if (strcmp(argv[argIndex], "trim-ownedcards") == 0) //won't add missing cards, instead removes missing cards from decks
2995
          {
2996
          opt_trim_unclaimed_decks = true;
2997
          }*/
2998
        else if (strcmp(argv[argIndex], "cl") == 0)
164✔
2999
        {
3000
            if (check_input_amount(argc, argv, argIndex, 1))
×
3001
                exit(1);
×
3002
            confidence_level = atof(argv[argIndex + 1]);
×
3003
            argIndex += 1;
×
3004
        }
3005
        else if (strcmp(argv[argIndex], "+so") == 0)
164✔
3006
        {
3007
            simplify_output = true;
×
3008
        }
3009
        else if (strcmp(argv[argIndex], "+uc") == 0)
164✔
3010
        {
3011
            print_upgraded = true;
×
3012
        }
3013
        else if (strcmp(argv[argIndex], "+vc") == 0)
164✔
3014
        {
3015
            print_values = true;
×
3016
        }
3017
        else if (strcmp(argv[argIndex], "+vc-x") == 0)
164✔
3018
        {
3019
            if (check_input_amount(argc, argv, argIndex, 1))
×
3020
                exit(1);
×
3021
            vc_x = atoi(argv[argIndex + 1]);
×
3022
            // print_values = true;
3023
            argIndex += 1;
×
3024
        }
3025
        else if (strcmp(argv[argIndex], "+ci") == 0)
164✔
3026
        {
3027
            show_ci = true;
×
3028
        }
3029
        else if (strcmp(argv[argIndex], "+hm") == 0)
164✔
3030
        {
3031
            use_harmonic_mean = true;
×
3032
        }
3033
        else if (strcmp(argv[argIndex], "no-fix") == 0)
164✔
3034
        {
3035
            for (int i = 0; i < Fix::num_fixes; ++i)
×
3036
                fixes[i] = false;
×
3037
        }
3038
        else if (strcmp(argv[argIndex], "fix-enhance-early") == 0)
164✔
3039
        {
3040
            fixes[Fix::enhance_early] = true;
×
3041
        }
3042
        else if (strcmp(argv[argIndex], "no-fix-enhance-early") == 0)
164✔
3043
        {
3044
            fixes[Fix::enhance_early] = false;
×
3045
        }
3046
        else if (strcmp(argv[argIndex], "fix-revenge-on-death") == 0)
164✔
3047
        {
3048
            fixes[Fix::revenge_on_death] = true;
×
3049
        }
3050
        else if (strcmp(argv[argIndex], "no-fix-revenge-on-death") == 0)
164✔
3051
        {
3052
            fixes[Fix::revenge_on_death] = false;
×
3053
        }
3054
        else if (strcmp(argv[argIndex], "fix-death-from-bge") == 0)
164✔
3055
        {
3056
            fixes[Fix::death_from_bge] = true;
×
3057
        }
3058
        else if (strcmp(argv[argIndex], "no-fix-death-from-bge") == 0)
164✔
3059
        {
3060
            fixes[Fix::death_from_bge] = false;
×
3061
        }
3062
        else if (strcmp(argv[argIndex], "fix-legion-under-megamorphosis") == 0)
164✔
3063
        {
3064
            fixes[Fix::legion_under_mega] = true;
×
3065
        }
3066
        else if (strcmp(argv[argIndex], "no-fix-legion-under-megamorphosis") == 0)
164✔
3067
        {
3068
            fixes[Fix::legion_under_mega] = false;
×
3069
        }
3070
        else if (strcmp(argv[argIndex], "update-barrier-each-turn") == 0)
164✔
3071
                {
3072
                        fixes[Fix::barrier_each_turn] = true;
×
3073
                }
3074
        else if (strcmp(argv[argIndex], "no-update-barrier-each-turn") == 0)
164✔
3075
                {
3076
                        fixes[Fix::barrier_each_turn] = false;
×
3077
                }
3078
        else if (strcmp(argv[argIndex], "update-dont-evade-mimic-selection") == 0)
164✔
3079
                {
3080
                        fixes[Fix::dont_evade_mimic_selection] = true;
×
3081
                }
3082
        else if (strcmp(argv[argIndex], "no-update-dont-evade-mimic-selection") == 0)
164✔
3083
                {
3084
                        fixes[Fix::dont_evade_mimic_selection] = false;
×
3085
                }
3086
        else if (strcmp(argv[argIndex], "update-leech-increase-max-hp") == 0)
164✔
3087
                {
3088
                        fixes[Fix::leech_increase_max_hp] = true;
×
3089
                }
3090
        else if (strcmp(argv[argIndex], "no-update-leech-increase-max-hp") == 0)
164✔
3091
                {
3092
                        fixes[Fix::leech_increase_max_hp] = false;
×
3093
                }
3094
        else if (strcmp(argv[argIndex], "update-counter-without-damage") == 0)
164✔
3095
                {
3096
                        fixes[Fix::counter_without_damage] = true;
×
3097
                }
3098
        else if (strcmp(argv[argIndex], "no-update-counter-without-damage") == 0)
164✔
3099
                {
3100
                        fixes[Fix::counter_without_damage] = false;
×
3101
                }
3102
        else if (strcmp(argv[argIndex], "update-corrosive-protect-armor") == 0)
164✔
3103
                {
3104
                        fixes[Fix::corrosive_protect_armor] = true;
×
3105
                }
3106
        else if (strcmp(argv[argIndex], "no-update-corrosive-protect-armor") == 0)
164✔
3107
                {
3108
                        fixes[Fix::corrosive_protect_armor] = false;
×
3109
                }
3110
        else if (strcmp(argv[argIndex], "update-poison-after-attacked") == 0)
164✔
3111
                {
3112
                        fixes[Fix::poison_after_attacked] = true;
×
3113
                }
3114
        else if (strcmp(argv[argIndex], "no-update-poison-after-attacked") == 0)
164✔
3115
                {
3116
                        fixes[Fix::poison_after_attacked] = false;
×
3117
                }
3118
        else if (strcmp(argv[argIndex], "update-subdue-before-attack") == 0)
164✔
3119
                {
3120
                        fixes[Fix::subdue_before_attack] = true;
×
3121
                }
3122
        else if (strcmp(argv[argIndex], "no-update-subdue-before-attack") == 0)
164✔
3123
                {
3124
                        fixes[Fix::subdue_before_attack] = false;
×
3125
                }
3126
        else if (strcmp(argv[argIndex], "update-reduce-delay-summoned-by-tower") == 0)
164✔
3127
        {
3128
            fixes[Fix::reduce_delay_summoned_by_tower] = true;
1✔
3129
        }
3130
        else if (strcmp(argv[argIndex], "no-update-reduce-delay-summoned-by-tower") == 0)
163✔
3131
        {
3132
            fixes[Fix::reduce_delay_summoned_by_tower] = false;
1✔
3133
        }
3134
        else if (strcmp(argv[argIndex], "seed") == 0)
162✔
3135
        {
3136
            if (check_input_amount(argc, argv, argIndex, 1))
81✔
3137
                exit(1);
×
3138
            sim_seed = atoi(argv[argIndex + 1]);
81✔
3139
            argIndex += 1;
81✔
3140
        }
3141
        else if (strcmp(argv[argIndex], "-v") == 0)
81✔
3142
        {
3143
            --debug_print;
×
3144
        }
3145
        else if (strcmp(argv[argIndex], "+v") == 0)
81✔
3146
        {
3147
            ++debug_print;
×
3148
        }
3149
        else if (strcmp(argv[argIndex], "strap") == 0)
81✔
3150
        {
NEW
3151
            debug_strap = true;
×
3152
        }
3153
        else if (strcmp(argv[argIndex], "vip") == 0)
81✔
3154
        {
3155
            if (check_input_amount(argc, argv, argIndex, 1))
×
3156
                exit(1);
×
3157
            opt_vip = argv[argIndex + 1];
×
3158
            argIndex += 1;
×
3159
        }
3160
        else if (strcmp(argv[argIndex], "allow-candidates") == 0)
81✔
3161
        {
3162
            if (check_input_amount(argc, argv, argIndex, 1))
×
3163
                exit(1);
×
3164
            opt_allow_candidates = argv[argIndex + 1];
×
3165
            argIndex += 1;
×
3166
        }
3167
        else if (strcmp(argv[argIndex], "disallow-candidates") == 0)
81✔
3168
        {
3169
            if (check_input_amount(argc, argv, argIndex, 1))
×
3170
                exit(1);
×
3171
            opt_disallow_candidates = argv[argIndex + 1];
×
3172
            argIndex += 1;
×
3173
        }
3174
        else if (strcmp(argv[argIndex], "disallow-recipes") == 0)
81✔
3175
        {
3176
            if (check_input_amount(argc, argv, argIndex, 1))
×
3177
                exit(1);
×
3178
            opt_disallow_recipes = argv[argIndex + 1];
×
3179
            argIndex += 1;
×
3180
        }
3181
        else if (strcmp(argv[argIndex], "hand") == 0) // set initial hand for test
81✔
3182
        {
3183
            if (check_input_amount(argc, argv, argIndex, 1))
×
3184
                exit(1);
×
3185
            opt_hand = argv[argIndex + 1];
×
3186
            argIndex += 1;
×
3187
        }
3188
        else if (strcmp(argv[argIndex], "enemy:hand") == 0) // set enemies' initial hand for test
81✔
3189
        {
3190
            if (check_input_amount(argc, argv, argIndex, 1))
×
3191
                exit(1);
×
3192
            opt_enemy_hand = argv[argIndex + 1];
×
3193
            argIndex += 1;
×
3194
        }
3195
        else if (strcmp(argv[argIndex], "yf") == 0 || strcmp(argv[argIndex], "yfort") == 0) // set forts
81✔
3196
        {
3197
            if (check_input_amount(argc, argv, argIndex, 1))
×
3198
                exit(1);
×
3199
            opt_forts = std::string(argv[argIndex + 1]);
×
3200
            argIndex += 1;
×
3201
        }
3202
        else if (strcmp(argv[argIndex], "yfpool") == 0 || strcmp(argv[argIndex], "yfortpool") == 0) // set forts
81✔
3203
        {
3204
            if (check_input_amount(argc, argv, argIndex, 1))
×
3205
                exit(1);
×
3206
            yfpool = std::stoi(argv[argIndex + 1]);
×
3207
            argIndex += 1;
×
3208
        }
3209
        else if (strcmp(argv[argIndex], "ef") == 0 || strcmp(argv[argIndex], "efort") == 0) // set enemies' forts
81✔
3210
        {
3211
            if (check_input_amount(argc, argv, argIndex, 1))
×
3212
                exit(1);
×
3213
            opt_enemy_forts = std::string(argv[argIndex + 1]);
×
3214
            argIndex += 1;
×
3215
        }
3216
        else if (strcmp(argv[argIndex], "efpool") == 0 || strcmp(argv[argIndex], "efortpool") == 0) // set forts
81✔
3217
        {
3218
            if (check_input_amount(argc, argv, argIndex, 1))
×
3219
                exit(1);
×
3220
            efpool = std::stoi(argv[argIndex + 1]);
×
3221
            argIndex += 1;
×
3222
        }
3223
        else if (strcmp(argv[argIndex], "yd") == 0 || strcmp(argv[argIndex], "ydom") == 0) // set dominions
81✔
3224
        {
3225
            if (check_input_amount(argc, argv, argIndex, 1))
×
3226
                exit(1);
×
3227
            opt_doms = std::string(argv[argIndex + 1]);
×
3228
            argIndex += 1;
×
3229
        }
3230
        else if (strcmp(argv[argIndex], "ed") == 0 || strcmp(argv[argIndex], "edom") == 0) // set enemies' dominions
81✔
3231
        {
3232
            if (check_input_amount(argc, argv, argIndex, 1))
×
3233
                exit(1);
×
3234
            opt_enemy_doms = std::string(argv[argIndex + 1]);
×
3235
            argIndex += 1;
×
3236
        }
3237
        else if (strcmp(argv[argIndex], "sim") == 0)
81✔
3238
        {
3239
            if (check_input_amount(argc, argv, argIndex, 1))
76✔
3240
                exit(1);
×
3241
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), 0u, simulate));
76✔
3242
            if (std::get<0>(opt_todo.back()) < 10)
76✔
3243
            {
3244
                opt_num_threads = 1;
×
3245
            }
3246
            argIndex += 1;
76✔
3247
        }
3248
        // climbing tasks
3249
        else if (strcmp(argv[argIndex], "climbex") == 0)
5✔
3250
        {
3251
            if (check_input_amount(argc, argv, argIndex, 2))
1✔
3252
                exit(1);
×
3253
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 2]), climb));
1✔
3254
            if (std::get<1>(opt_todo.back()) < 10)
1✔
3255
            {
3256
                opt_num_threads = 1;
×
3257
            }
3258
            opt_do_optimization = true;
1✔
3259
            opt_multi_optimization = true;
1✔
3260
            argIndex += 2;
1✔
3261
        }
3262
        else if (strcmp(argv[argIndex], "climb") == 0)
4✔
3263
        {
3264
            if (check_input_amount(argc, argv, argIndex, 1))
1✔
3265
                exit(1);
×
3266
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 1]), climb));
1✔
3267
            if (std::get<1>(opt_todo.back()) < 10)
1✔
3268
            {
3269
                opt_num_threads = 1;
×
3270
            }
3271
            opt_do_optimization = true;
1✔
3272
            opt_multi_optimization = true;
1✔
3273
            argIndex += 1;
1✔
3274
        }
3275
        else if (strcmp(argv[argIndex], "climb_forts") == 0)
3✔
3276
        {
3277
            if (check_input_amount(argc, argv, argIndex, 1))
×
3278
                exit(1);
×
3279
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 1]), climb_forts));
×
3280
            if (std::get<1>(opt_todo.back()) < 10)
×
3281
            {
3282
                opt_num_threads = 1;
×
3283
            }
3284
            argIndex += 1;
×
3285
        }
3286
        else if (strcmp(argv[argIndex], "anneal") == 0)
3✔
3287
        {
3288
            if (check_input_amount(argc, argv, argIndex, 3))
1✔
3289
                exit(1);
×
3290
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 1]), anneal));
1✔
3291
            temperature = std::stod(argv[argIndex + 2]);
2✔
3292
            coolingRate = std::stod(argv[argIndex + 3]);
2✔
3293
            if (std::get<1>(opt_todo.back()) < 10)
1✔
3294
            {
3295
                opt_num_threads = 1;
×
3296
            }
3297
            opt_do_optimization = true;
1✔
3298
            opt_multi_optimization = true;
1✔
3299
            argIndex += 3;
1✔
3300
        }
3301
        else {
3302
            tokenParsed = false;
3303
        }
3304
        
3305
        if (!tokenParsed)
407✔
3306
        {
3307
        //no indent: keep two parts of a nested if at the same level
3308
        if (strcmp(argv[argIndex], "genetic") == 0)
2✔
3309
        {
3310
            if (check_input_amount(argc, argv, argIndex, 1))
1✔
3311
                exit(1);
×
3312
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 1]), genetic));
1✔
3313
            if (std::get<1>(opt_todo.back()) < 10)
1✔
3314
            {
3315
                opt_num_threads = 1;
×
3316
            }
3317
            opt_do_optimization = true;
1✔
3318
            opt_multi_optimization = true;
1✔
3319
            argIndex += 1;
1✔
3320
        }
3321
        else if (strcmp(argv[argIndex], "genetic-pool") == 0 || strcmp(argv[argIndex], "beam-size") == 0 || strcmp(argv[argIndex], "size") == 0)
1✔
3322
        {
3323
            if (check_input_amount(argc, argv, argIndex, 1))
×
3324
                exit(1);
×
3325
            pool_size = std::stod(argv[argIndex + 1]);
×
3326
            argIndex += 1;
×
3327
        }
3328
        else if (strcmp(argv[argIndex], "genetic-gen") == 0)
1✔
3329
        {
3330
            if (check_input_amount(argc, argv, argIndex, 1))
×
3331
                exit(1);
×
3332
            generations = std::stod(argv[argIndex + 1]);
×
3333
            argIndex += 1;
×
3334
        }
3335
        else if (strcmp(argv[argIndex], "genetic-opts") == 0)
1✔
3336
        {
3337
            if (check_input_amount(argc, argv, argIndex, 3))
×
3338
                exit(1);
×
3339
            opt_pool_keep = std::stod(argv[argIndex + 1]);
×
3340
            opt_pool_cross = std::stod(argv[argIndex + 2]);
×
3341
            opt_pool_mutate = std::stod(argv[argIndex + 3]);
×
3342
            argIndex += 3;
×
3343
        }
3344
        else if (strcmp(argv[argIndex], "beam") == 0)
1✔
3345
        {
3346
            if (check_input_amount(argc, argv, argIndex, 1))
1✔
3347
                exit(1);
×
3348
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 1]), beam));
1✔
3349
            if (std::get<1>(opt_todo.back()) < 10)
1✔
3350
            {
3351
                opt_num_threads = 1;
×
3352
            }
3353
            opt_do_optimization = true;
1✔
3354
            opt_multi_optimization = true;
1✔
3355
            argIndex += 1;
1✔
3356
        }
3357
        else if (strcmp(argv[argIndex], "reorder") == 0)
×
3358
        {
3359
            if (check_input_amount(argc, argv, argIndex, 1))
×
3360
                exit(1);
×
3361
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 1]), reorder));
×
3362
            if (std::get<1>(opt_todo.back()) < 10)
×
3363
            {
3364
                opt_num_threads = 1;
×
3365
            }
3366
            opt_do_reorder = true;
×
3367
            argIndex += 1;
×
3368
        }
3369
        else if (strncmp(argv[argIndex], "scale-opts:", 11) == 0)
×
3370
        {
3371
            std::string climb_opts_str(argv[argIndex] + 11);
×
3372
            boost::tokenizer<boost::char_delimiters_separator<char>> climb_opts{climb_opts_str, boost::char_delimiters_separator<char>{false, ",", ""}};
×
3373
            for (const auto &opt : climb_opts)
×
3374
            {
3375
                const auto dot_pos = opt.find(".");
×
3376
                const auto slash_pos = opt.find("/");
×
3377
                const bool has_value = (dot_pos != std::string::npos);
×
3378
                if (slash_pos == std::string::npos)
×
3379
                    throw std::runtime_error("scale-opts:" + opt + " requires an argument");
×
3380
                const std::string &opt_type = has_value ? opt.substr(0, dot_pos) : "";
×
3381
                const std::string opt_name{has_value ? opt.substr(dot_pos + 1, slash_pos - dot_pos - 1) : opt.substr(0, slash_pos)};
×
3382
                const std::string opt_value{opt.substr(slash_pos + 1)};
×
3383
                if ((opt_name == "hp"))
×
3384
                {
3385
                    hp_scale = atof(opt_value.c_str());
×
3386
                }
3387
                else if ((opt_name == "atk"))
×
3388
                {
3389
                    atk_scale = atof(opt_value.c_str());
×
3390
                }
3391
                else if (opt_name == "x")
×
3392
                {
3393
                    x_skill_scale[skill_name_to_id(opt_type)] = atof(opt_value.c_str());
×
3394
                }
3395
                else if (opt_name == "n")
×
3396
                {
3397
                    n_skill_scale[skill_name_to_id(opt_type)] = atof(opt_value.c_str());
×
3398
                }
3399
                else if (opt_name == "c")
×
3400
                {
3401
                    c_skill_scale[skill_name_to_id(opt_type)] = atof(opt_value.c_str());
×
3402
                }
3403
                else
3404
                {
3405
                    std::cerr << "Error: Unknown scale option " << opt_name << " of " << opt_type;
×
3406
                    if (has_value)
×
3407
                    {
3408
                        std::cerr << " (value is: " << opt_value << ")";
×
3409
                    }
3410
                    std::cerr << std::endl;
×
3411
                    exit(1);
×
3412
                }
3413
            }
×
3414
        }
×
3415
        // climbing options
3416
        else if (strncmp(argv[argIndex], "climb-opts:", 11) == 0)
×
3417
        {
3418
            std::string climb_opts_str(argv[argIndex] + 11);
×
3419
            boost::tokenizer<boost::char_delimiters_separator<char>> climb_opts{climb_opts_str, boost::char_delimiters_separator<char>{false, ",", ""}};
×
3420
            for (const auto &opt : climb_opts)
×
3421
            {
3422
                const auto delim_pos = opt.find("=");
×
3423
                const bool has_value = (delim_pos != std::string::npos);
×
3424
                const std::string &opt_name = has_value ? opt.substr(0, delim_pos) : opt;
×
3425
                const std::string opt_value{has_value ? opt.substr(delim_pos + 1) : opt};
×
3426
                auto ensure_opt_value = [](const bool has_value, const std::string &opt_name)
×
3427
                {
3428
                    if (!has_value)
×
3429
                    {
3430
                        throw std::runtime_error("climb-opts:" + opt_name + " requires an argument");
×
3431
                    }
3432
                };
×
3433
                if ((opt_name == "iter-mul") or (opt_name == "iterations-multiplier"))
×
3434
                {
3435
                    ensure_opt_value(has_value, opt_name);
×
3436
                    iterations_multiplier = std::stoi(opt_value);
×
3437
                }
3438
                else if ((opt_name == "egc") or (opt_name == "endgame-commander") or (opt_name == "min-commander-fusion-level"))
×
3439
                {
3440
                    ensure_opt_value(has_value, opt_name);
×
3441
                    use_fused_commander_level = std::stoi(opt_value);
×
3442
                }
3443
                else if (opt_name == "use-all-commander-levels")
×
3444
                {
3445
                    use_top_level_commander = false;
×
3446
                }
3447
                else if (opt_name == "use-all-card-levels")
×
3448
                {
3449
                    use_top_level_card = false;
×
3450
                }
3451
                else if ((opt_name == "recent-boost") or (opt_name == "rb")) // prefer new cards in hill climb and break climb loop faster
×
3452
                {
3453
                    prefered_recent = true;
×
3454
                }
3455
                else if ((opt_name == "recent-boost-times") or (opt_name == "rbt")) // prefer new cards in hill climb and break climb loop faster
×
3456
                {
3457
                    ensure_opt_value(has_value, opt_name);
×
3458
                    prefered_factor = std::stoi(opt_value);
×
3459
                }
3460
                else if ((opt_name == "recent-boost-percent") or (opt_name == "rbp")) // prefer new cards in hill climb and break climb loop faster
×
3461
                {
3462
                    ensure_opt_value(has_value, opt_name);
×
3463
                    recent_percent = std::stoi(opt_value);
×
3464
                }
3465
                else if ((opt_name == "otd") or (opt_name == "open-the-deck"))
×
3466
                {
3467
                    mode_open_the_deck = true;
×
3468
                }
3469
                else
3470
                {
3471
                    std::cerr << "Error: Unknown climb option " << opt_name;
×
3472
                    if (has_value)
×
3473
                    {
3474
                        std::cerr << " (value is: " << opt_value << ")";
×
3475
                    }
3476
                    std::cerr << std::endl;
×
3477
                    exit(1);
×
3478
                }
3479
            }
×
3480
        }
×
3481
        else if (strcmp(argv[argIndex], "debug") == 0)
×
3482
        {
3483
            opt_todo.push_back(std::make_tuple(0u, 0u, debug));
×
3484
            opt_num_threads = 1;
×
3485
            // disable saving to db
3486
            use_db_write= false;
×
3487
            use_db_load = false;
×
3488
        }
3489
        else if (strcmp(argv[argIndex], "debuguntil") == 0)
×
3490
        {
3491
            // output the debug info for the first battle that min_score <= score <= max_score.
3492
            // E.g., 0 0: lose; 100 100: win (non-raid); 20 100: at least 20 damage (raid).
3493
            if (check_input_amount(argc, argv, argIndex, 2))
×
3494
                exit(1);
×
3495
            opt_todo.push_back(std::make_tuple((unsigned)atoi(argv[argIndex + 1]), (unsigned)atoi(argv[argIndex + 2]), debuguntil));
×
3496
            opt_num_threads = 1;
×
3497
            argIndex += 2;
×
3498
            // disable saving to db
3499
            use_db_write= false;
×
3500
            use_db_load = false;
×
3501
        }
3502
        else
3503
        {
3504
            std::cerr << "Error: Unknown option " << argv[argIndex] << std::endl;
×
3505
            exit(1);
×
3506
        }
3507
        } // if (tokenParsed)
3508
    }
3509
    load_db(prefix);
162✔
3510
    load_ml(prefix);
162✔
3511

3512
#ifdef _OPENMP
3513
    opt_num_threads = omp_get_max_threads();
3514
#endif
3515
    // delete, since prefix/suffix might change we reload all cards.
3516
    // redundant to calling init() before run()
3517
    all_cards.clear();
81✔
3518
    owned_alpha_dominion = nullptr;
81✔
3519

3520
    all_cards = Cards();
81✔
3521
    Decks decks;
81✔
3522
    std::unordered_map<std::string, std::string> bge_aliases;
81✔
3523
    load_skills_set_xml(all_cards, prefix + "data/skills_set.xml", true);
81✔
3524
    for (unsigned section = 1;
1,744✔
3525
         load_cards_xml(all_cards, prefix + "data/cards_section_" + std::to_string(section) + ".xml", false);
3,488✔
3526
         ++section)
3527
        ;
3528
    all_cards.organize();
81✔
3529
    load_levels_xml(all_cards, prefix + "data/levels.xml", true);
81✔
3530
    all_cards.fix_dominion_recipes();
81✔
3531
    for (const auto &suffix : fn_suffix_list)
162✔
3532
    {
3533
        load_decks_xml(decks, all_cards, prefix + "data/missions" + suffix + ".xml", prefix + "data/raids" + suffix + ".xml", suffix.empty());
486✔
3534
        load_recipes_xml(all_cards, prefix + "data/fusion_recipes_cj2" + suffix + ".xml", suffix.empty());
324✔
3535
        read_card_abbrs(all_cards, prefix + "data/cardabbrs" + suffix + ".txt");
324✔
3536
    }
3537
    for (const auto &suffix : fn_suffix_list)
162✔
3538
    {
3539
        load_custom_decks(decks, all_cards, prefix + "data/customdecks" + suffix + ".txt");
324✔
3540
        map_keys_to_set(read_custom_cards(all_cards, prefix + "data/allowed_candidates" + suffix + ".txt", false), allowed_candidates);
324✔
3541
        map_keys_to_set(read_custom_cards(all_cards, prefix + "data/disallowed_candidates" + suffix + ".txt", false), disallowed_candidates);
324✔
3542
        map_keys_to_set(read_custom_cards(all_cards, prefix + "data/disallowed_recipes" + suffix + ".txt", false), disallowed_recipes);
324✔
3543
    }
3544

3545
    read_bge_aliases(bge_aliases, prefix + "data/bges.txt");
81✔
3546

3547
    fill_skill_table();
81✔
3548

3549
    if (opt_do_optimization and use_owned_cards)
81✔
3550
    {
3551
        if (opt_owned_cards_str_list.empty())
5✔
3552
        { // load default files only if specify no -o=
3553
            for (const auto &suffix : fn_suffix_list)
10✔
3554
            {
3555
                std::string filename = prefix + "data/ownedcards" + suffix + ".txt";
15✔
3556
                if (boost::filesystem::exists(filename))
15✔
3557
                {
3558
                    opt_owned_cards_str_list.push_back(filename);
5✔
3559
                }
3560
            }
5✔
3561
        }
3562
        std::map<unsigned, unsigned> _owned_cards;
5✔
3563
        for (const auto &oc_str : opt_owned_cards_str_list)
10✔
3564
        {
3565
            read_owned_cards(all_cards, _owned_cards, oc_str);
5✔
3566
        }
3567

3568
        // keep only one copy of alpha dominion
3569
        for (auto owned_it = _owned_cards.begin(); owned_it != _owned_cards.end();)
125✔
3570
        {
3571
            const Card *owned_card = all_cards.by_id(owned_it->first);
120✔
3572
            bool need_remove = (!owned_it->second);
120✔
3573
            if (!need_remove && (owned_card->m_category == CardCategory::dominion_alpha))
120✔
3574
            {
3575
                if (!owned_alpha_dominion)
15✔
3576
                {
3577
                    owned_alpha_dominion = owned_card;
5✔
3578
                }
3579
                else
3580
                {
3581
                    /*std::cerr << "Warning: ownedcards already contains alpha dominion (" << owned_alpha_dominion->m_name
3582
                      << "): removing additional " << owned_card->m_name << std::endl;
3583
                      need_remove = true;*/
3584
                }
3585
            }
3586
            if (need_remove)
120✔
3587
            {
3588
                owned_it = _owned_cards.erase(owned_it);
×
3589
            }
3590
            else
3591
            {
3592
                ++owned_it;
120✔
3593
            }
3594
        }
3595
        if (!owned_alpha_dominion && use_owned_dominions)
5✔
3596
        {
3597
            // owned_alpha_dominion = all_cards.by_id(50002);
3598
            // std::cerr << "Warning: dominion climbing enabled and no alpha dominion found in owned cards, adding default "
3599
            //     << owned_alpha_dominion->m_name << std::endl;
3600
        }
3601
        if (owned_alpha_dominion)
5✔
3602
        {
3603
            _owned_cards[owned_alpha_dominion->m_id] = 1;
5✔
3604
        }
3605

3606
        // remap owned cards to unordered map (should be quicker for searching)
3607
        owned_cards.reserve(_owned_cards.size());
5✔
3608
        for (auto owned_it = _owned_cards.begin(); owned_it != _owned_cards.end(); ++owned_it)
125✔
3609
        {
3610
            owned_cards[owned_it->first] = owned_it->second;
120✔
3611
        }
3612
    }
5✔
3613

3614
    // parse BGEs
3615
    opt_bg_effects[0].fill(0);
2,268✔
3616
    opt_bg_effects[1].fill(0);
324✔
3617
    for (int player = 2; player >= 0; --player)
324✔
3618
    {
3619
        for (auto &&opt_effect : opt_effects[player])
303✔
3620
        {
3621
            std::unordered_set<std::string> used_bge_aliases;
60✔
3622
            if (!parse_bge(opt_effect, player, bge_aliases, opt_bg_effects[0], opt_bg_effects[1], opt_bg_skills[0], opt_bg_skills[1], used_bge_aliases))
180✔
3623
            {
3624
                exit(1);
×
3625
            }
3626
        }
60✔
3627
    }
3628

3629
    // parse allowed candidates from options
3630
    try
81✔
3631
    {
3632
        auto &&id_marks = string_to_ids(all_cards, opt_allow_candidates, "allowed-candidates");
81✔
3633
        for (const auto &cid : id_marks.first)
81✔
3634
        {
3635
            allowed_candidates.insert(cid);
×
3636
        }
3637
    }
×
3638
    catch (const std::runtime_error &e)
×
3639
    {
3640
        std::cerr << "Error: allow-candidates " << opt_allow_candidates << ": " << e.what() << std::endl;
×
3641
        exit(1);
×
3642
    }
×
3643

3644
    // parse disallowed candidates from options
3645
    try
81✔
3646
    {
3647
        auto &&id_marks = string_to_ids(all_cards, opt_disallow_candidates, "disallowed-candidates");
81✔
3648
        for (const auto &cid : id_marks.first)
81✔
3649
        {
3650
            disallowed_candidates.insert(cid);
×
3651
        }
3652
    }
×
3653
    catch (const std::runtime_error &e)
×
3654
    {
3655
        std::cerr << "Error: disallow-candidates " << opt_disallow_candidates << ": " << e.what() << std::endl;
×
3656
        exit(1);
×
3657
    }
×
3658

3659
    // parse & drop disallowed recipes
3660
    try
81✔
3661
    {
3662
        auto &&id_dis_recipes = string_to_ids(all_cards, opt_disallow_recipes, "disallowed-recipes");
81✔
3663
        for (auto &cid : id_dis_recipes.first)
81✔
3664
        {
3665
            all_cards.erase_fusion_recipe(cid);
×
3666
        }
3667
    }
×
3668
    catch (const std::runtime_error &e)
×
3669
    {
3670
        std::cerr << "Error: disallow-recipes " << opt_disallow_recipes << ": " << e.what() << std::endl;
×
3671
        exit(1);
×
3672
    }
×
3673
    for (auto cid : disallowed_recipes)
81✔
3674
    {
3675
        all_cards.erase_fusion_recipe(cid);
×
3676
    }
3677

3678
#ifndef NQUEST
3679
    if (!opt_quest.empty())
3680
    {
3681
        try
3682
        {
3683
            optimization_mode = OptimizationMode::quest;
3684
            std::vector<std::string> tokens;
3685
            boost::split(tokens, opt_quest, boost::is_any_of(" -"));
3686
            if (tokens.size() < 3)
3687
            {
3688
                throw std::runtime_error("Expect one of: su n skill; sd n skill; cu n faction/strcture; ck n structure");
3689
            }
3690
            auto type_str = boost::to_lower_copy(tokens[0]);
3691
            tuo::quest.quest_value = boost::lexical_cast<unsigned>(tokens[1]);
3692
            auto key_str = boost::to_lower_copy(tokens[2]);
3693
            unsigned quest_index = 3;
3694
            if (type_str == "su" || type_str == "sd")
3695
            {
3696
                Skill::Skill skill_id = skill_name_to_id(key_str);
3697
                if (skill_id == Skill::no_skill)
3698
                {
3699
                    std::cerr << "Error: Expect skill in quest \"" << opt_quest << "\".\n";
3700
                    exit(1);
3701
                }
3702
                tuo::quest.quest_type = type_str == "su" ? QuestType::skill_use : QuestType::skill_damage;
3703
                tuo::quest.quest_key = skill_id;
3704
            }
3705
            else if (type_str == "cu" || type_str == "ck")
3706
            {
3707
                if (key_str == "assault")
3708
                {
3709
                    tuo::quest.quest_type = type_str == "cu" ? QuestType::type_card_use : QuestType::type_card_kill;
3710
                    tuo::quest.quest_key = CardType::assault;
3711
                }
3712
                else if (key_str == "structure")
3713
                {
3714
                    tuo::quest.quest_type = type_str == "cu" ? QuestType::type_card_use : QuestType::type_card_kill;
3715
                    tuo::quest.quest_key = CardType::structure;
3716
                }
3717
                else
3718
                {
3719
                    for (unsigned i = 1; i < Faction::num_factions; ++i)
3720
                    {
3721
                        if (key_str == boost::to_lower_copy(faction_names[i]))
3722
                        {
3723
                            tuo::quest.quest_type = type_str == "cu" ? QuestType::faction_assault_card_use : QuestType::faction_assault_card_kill;
3724
                            tuo::quest.quest_key = i;
3725
                            break;
3726
                        }
3727
                    }
3728
                    if (tuo::quest.quest_key == 0)
3729
                    {
3730
                        std::cerr << "Error: Expect assault, structure or faction in quest \"" << opt_quest << "\".\n";
3731
                        exit(1);
3732
                    }
3733
                }
3734
            }
3735
            else if (type_str == "cs")
3736
            {
3737
                unsigned card_id;
3738
                unsigned card_num;
3739
                char num_sign;
3740
                char mark;
3741
                try
3742
                {
3743
                    parse_card_spec(all_cards, key_str, card_id, card_num, num_sign, mark);
3744
                    tuo::quest.quest_type = QuestType::card_survival;
3745
                    tuo::quest.quest_key = card_id;
3746
                }
3747
                catch (const std::runtime_error &e)
3748
                {
3749
                    std::cerr << "Error: Expect a card in quest \"" << opt_quest << "\".\n";
3750
                    exit(1);
3751
                }
3752
            }
3753
            else if (type_str == "suoc" && tokens.size() >= 4)
3754
            {
3755
                Skill::Skill skill_id = skill_name_to_id(key_str);
3756
                if (skill_id == Skill::no_skill)
3757
                {
3758
                    std::cerr << "Error: Expect skill in quest \"" << opt_quest << "\".\n";
3759
                    exit(1);
3760
                }
3761
                unsigned card_id;
3762
                unsigned card_num;
3763
                char num_sign;
3764
                char mark;
3765
                try
3766
                {
3767
                    parse_card_spec(all_cards, boost::to_lower_copy(tokens[3]), card_id, card_num, num_sign, mark);
3768
                    quest_index += 1;
3769
                    tuo::quest.quest_type = QuestType::skill_use;
3770
                    tuo::quest.quest_key = skill_id;
3771
                    tuo::quest.quest_2nd_key = card_id;
3772
                }
3773
                catch (const std::runtime_error &e)
3774
                {
3775
                    std::cerr << "Error: Expect a card in quest \"" << opt_quest << "\".\n";
3776
                    exit(1);
3777
                }
3778
            }
3779
            else
3780
            {
3781
                throw std::runtime_error("Expect one of: su n skill; sd n skill; cu n faction/strcture; ck n structure");
3782
            }
3783
            tuo::quest.quest_score = tuo::quest.quest_value;
3784
            for (unsigned i = quest_index; i < tokens.size(); ++i)
3785
            {
3786
                const auto &token = tokens[i];
3787
                if (token == "each")
3788
                {
3789
                    tuo::quest.must_fulfill = true;
3790
                    tuo::quest.quest_score = 100;
3791
                }
3792
                else if (token == "win")
3793
                {
3794
                    tuo::quest.must_win = true;
3795
                }
3796
                else if (token.substr(0, 2) == "q=")
3797
                {
3798
                    tuo::quest.quest_score = boost::lexical_cast<unsigned>(token.substr(2));
3799
                }
3800
                else if (token.substr(0, 2) == "w=")
3801
                {
3802
                    tuo::quest.win_score = boost::lexical_cast<unsigned>(token.substr(2));
3803
                }
3804
                else
3805
                {
3806
                    throw std::runtime_error("Cannot recognize " + token);
3807
                }
3808
            }
3809
            max_possible_score[(size_t)optimization_mode] = tuo::quest.quest_score + tuo::quest.win_score;
3810
        }
3811
        catch (const boost::bad_lexical_cast &e)
3812
        {
3813
            std::cerr << "Error: Expect a number in quest \"" << opt_quest << "\".\n";
3814
            exit(1);
3815
        }
3816
        catch (const std::runtime_error &e)
3817
        {
3818
            std::cerr << "Error: quest " << opt_quest << ": " << e.what() << std::endl;
3819
            exit(1);
3820
        }
3821
    }
3822
#endif
3823

3824
    // std::string your_deck_name{argv[1]};
3825
    auto &&your_deck_list_parsed = parse_deck_list(your_deck_list, decks);
162✔
3826
    auto &&enemy_deck_list_parsed = parse_deck_list(enemy_deck_list, decks);
162✔
3827

3828
    // Deck* your_deck{nullptr};
3829
    std::vector<Deck *> your_decks;
81✔
3830
    std::vector<Deck *> enemy_decks;
81✔
3831
    std::vector<long double> your_decks_factors;
81✔
3832
    std::vector<long double> enemy_decks_factors;
81✔
3833
    for (auto deck_parsed : your_deck_list_parsed)
390✔
3834
    {
3835
        Deck *your_deck{nullptr};
309✔
3836
        try
309✔
3837
        {
3838
            your_deck = find_deck(decks, all_cards, deck_parsed.first)->clone();
618✔
3839
        }
3840
        catch (const std::runtime_error &e)
×
3841
        {
3842
            std::cerr << "Error: Deck " << deck_parsed.first << ": " << e.what() << std::endl;
×
3843
            exit(1);
×
3844
        }
×
3845
        if (your_deck == nullptr)
309✔
3846
        {
3847
            std::cerr << "Error: Invalid attack deck name/hash " << deck_parsed.first << ".\n";
×
3848
            exit(1);
×
3849
        }
3850
        else if (!your_deck->variable_cards.empty())
309✔
3851
        {
3852
            std::cerr << "Error: Invalid attack deck " << deck_parsed.first << ": has optional cards.\n";
×
3853
            your_deck = nullptr;
×
3854
        }
3855
        else if (!your_deck->variable_forts.empty())
309✔
3856
        {
3857
            std::cerr << "Error: Invalid attack deck " << deck_parsed.first << ": has optional cards.\n";
×
3858
            your_deck = nullptr;
×
3859
        }
3860
        if (your_deck == nullptr)
309✔
3861
        {
3862
            usage(argc, argv);
×
3863
            exit(1);
×
3864
            // return 255;
3865
        }
3866

3867
        your_deck->strategy = opt_your_strategy;
309✔
3868
        if (!opt_forts.empty())
309✔
3869
        {
3870
            try
×
3871
            {
3872
                if (!yfpool)
×
3873
                    your_deck->add_forts(opt_forts + ",");
×
3874
                else
3875
                    your_deck->add_pool_forts(opt_forts + ",", yfpool);
×
3876
            }
3877
            catch (const std::runtime_error &e)
×
3878
            {
3879
                std::cerr << "Error: yfort " << opt_forts << ": " << e.what() << std::endl;
×
3880
                exit(1);
×
3881
            }
×
3882
        }
3883
        if (!opt_doms.empty())
309✔
3884
        {
3885
            try
×
3886
            {
3887
                your_deck->add_dominions(opt_doms + ",", true);
×
3888
            }
3889
            catch (const std::runtime_error &e)
×
3890
            {
3891
                std::cerr << "Error: ydom " << opt_doms << ": " << e.what() << std::endl;
×
3892
                exit(1);
×
3893
            }
×
3894
        }
3895

3896
        try
309✔
3897
        {
3898
            your_deck->set_vip_cards(opt_vip);
309✔
3899
        }
3900
        catch (const std::runtime_error &e)
×
3901
        {
3902
            std::cerr << "Error: vip " << opt_vip << ": " << e.what() << std::endl;
×
3903
            exit(1);
×
3904
        }
×
3905

3906
        try
309✔
3907
        {
3908
            your_deck->set_given_hand(opt_hand);
309✔
3909
        }
3910
        catch (const std::runtime_error &e)
×
3911
        {
3912
            std::cerr << "Error: hand " << opt_hand << ": " << e.what() << std::endl;
×
3913
            exit(1);
×
3914
        }
×
3915

3916
        // add cards from the decks to requirement/inventory
3917
        if (opt_do_optimization || opt_do_reorder)
309✔
3918
        {
3919
            if (opt_keep_commander) // TODO this does not work with multi deck mode
5✔
3920
            {
3921
                requirement.num_cards[your_deck->commander] = 1;
×
3922
            }
3923
            for (auto &&card_mark : your_deck->card_marks)
5✔
3924
            {
3925
                auto &&card = card_mark.first < 0 ? your_deck->commander : your_deck->cards[card_mark.first];
×
3926
                auto mark = card_mark.second;
×
3927
                if ((mark == '!') && ((card_mark.first >= 0) || !opt_keep_commander))
×
3928
                {
3929
                    requirement.num_cards[card] += 1;
×
3930
                }
3931
            }
3932
            if (opt_skip_unclaimed_decks)
5✔
3933
            {
3934
                // skip decks that can not be build
3935
                if (claim_cards_needed({your_deck->commander}))
×
3936
                    continue;
×
3937
                if (claim_cards_needed(your_deck->cards))
×
3938
                    continue;
×
3939
                if (your_deck->alpha_dominion && claim_cards_needed({your_deck->alpha_dominion}))
×
3940
                    continue;
×
3941
            }
3942
            else if (opt_do_optimization and use_owned_cards)
5✔
3943
            {
3944
                // Force to claim cards in your initial deck.
3945
                claim_cards({your_deck->commander});
5✔
3946
                claim_cards(your_deck->cards);
5✔
3947
                if (your_deck->alpha_dominion)
5✔
3948
                    claim_cards({your_deck->alpha_dominion});
×
3949
            }
3950

3951
            // shrink any oversized deck to maximum of 10 cards + commander
3952
            // NOTE: do this AFTER the call to claim_cards so that passing an initial deck of >10 cards
3953
            //       can be used as a "shortcut" for adding them to owned cards. Also this allows climb
3954
            //       to figure out which are the best 10, rather than restricting climb to the first 10.
3955
            if (your_deck->cards.size() > max_deck_len)
5✔
3956
            {
3957
                your_deck->shrink(max_deck_len);
×
3958
                if (debug_print >= 0)
×
3959
                {
3960
                    std::cerr << "WARNING: Too many cards in your deck. Trimmed.\n";
×
3961
                }
3962
            }
3963
        }
3964

3965
        your_decks.push_back(your_deck);
309✔
3966
        your_decks_factors.push_back(deck_parsed.second);
309✔
3967
    }
309✔
3968
    if (your_decks.size() == 0)
81✔
3969
    {
3970
        std::cerr << "No deck set. Probably due to 'strict-ownedcards' and a deck that is incompatible to the set 'ownedcards'." << std::endl;
×
3971
        exit(1);
×
3972
    }
3973
    target_score = opt_target_score.empty() ? max_possible_score[(size_t)optimization_mode] : boost::lexical_cast<long double>(opt_target_score);
81✔
3974

3975
    for (auto deck_parsed : enemy_deck_list_parsed)
390✔
3976
    {
3977
        Deck *enemy_deck{nullptr};
309✔
3978
        try
309✔
3979
        {
3980
            enemy_deck = find_deck(decks, all_cards, deck_parsed.first);
618✔
3981
        }
3982
        catch (const std::runtime_error &e)
×
3983
        {
3984
            std::cerr << "Error: Deck " << deck_parsed.first << ": " << e.what() << std::endl;
×
3985
            exit(1);
×
3986
        }
×
3987
        if (enemy_deck == nullptr)
309✔
3988
        {
3989
            std::cerr << "Error: Invalid defense deck name/hash " << deck_parsed.first << ".\n";
×
3990
            usage(argc, argv);
×
3991
            exit(1);
×
3992
        }
3993
        if (optimization_mode == OptimizationMode::notset)
309✔
3994
        {
3995
            if (enemy_deck->decktype == DeckType::raid)
81✔
3996
            {
3997
                optimization_mode = OptimizationMode::raid;
×
3998
            }
3999
            else if (enemy_deck->decktype == DeckType::campaign)
81✔
4000
            {
4001
                gamemode = surge;
×
4002
                optimization_mode = OptimizationMode::campaign;
×
4003
            }
4004
            else
4005
            {
4006
                optimization_mode = OptimizationMode::winrate;
81✔
4007
            }
4008
        }
4009
        enemy_deck->strategy = opt_enemy_strategy;
309✔
4010
        if (!opt_enemy_doms.empty())
309✔
4011
        {
4012
            try
×
4013
            {
4014
                enemy_deck->add_dominions(opt_enemy_doms + ",", true);
×
4015
            }
4016
            catch (const std::runtime_error &e)
×
4017
            {
4018
                std::cerr << "Error: edom " << opt_enemy_doms << ": " << e.what() << std::endl;
×
4019
                exit(1);
×
4020
            }
×
4021
        }
4022
        if (!opt_enemy_forts.empty())
309✔
4023
        {
4024
            try
×
4025
            {
4026
                if (!efpool)
×
4027
                    enemy_deck->add_forts(opt_enemy_forts + ",");
×
4028
                else
4029
                    enemy_deck->add_pool_forts(opt_enemy_forts + ",", efpool);
×
4030
            }
4031
            catch (const std::runtime_error &e)
×
4032
            {
4033
                std::cerr << "Error: efort " << opt_enemy_forts << ": " << e.what() << std::endl;
×
4034
                exit(1);
×
4035
            }
×
4036
        }
4037
        try
309✔
4038
        {
4039
            enemy_deck->set_given_hand(opt_enemy_hand);
309✔
4040
        }
4041
        catch (const std::runtime_error &e)
×
4042
        {
4043
            std::cerr << "Error: enemy:hand " << opt_enemy_hand << ": " << e.what() << std::endl;
×
4044
            exit(1);
×
4045
        }
×
4046
        enemy_decks.push_back(enemy_deck);
309✔
4047
        enemy_decks_factors.push_back(deck_parsed.second);
309✔
4048
    }
309✔
4049

4050
    std::vector<long double> factors((opt_multi_optimization ? 1 : your_decks_factors.size()) * enemy_decks_factors.size());
81✔
4051
    for (unsigned i = 0; i < factors.size(); ++i)
2,138✔
4052
    {
4053
        factors[i] = your_decks_factors[i / enemy_decks_factors.size()] * enemy_decks_factors[i % enemy_decks_factors.size()];
2,057✔
4054
    }
4055

4056
    if ((opt_do_optimization || opt_do_reorder) && (your_decks.size() != 1 && !opt_multi_optimization))
81✔
4057
    {
4058
        std::cerr << "Optimization only works with a single deck" << std::endl;
×
4059
        exit(1);
×
4060
    }
4061

4062
    if (debug_print >= 0)
81✔
4063
    {
4064
        for (unsigned i(0); i < your_decks.size(); ++i)
390✔
4065
        {
4066
            auto your_deck = your_decks[i];
309✔
4067
            std::cout << "Your Deck:" << your_decks_factors[i] << ": " << (debug_print > 0 ? your_deck->long_description() : your_deck->medium_description()) << std::endl;
618✔
4068
        }
4069
        for (unsigned bg_effect = PassiveBGE::no_bge; bg_effect < PassiveBGE::num_passive_bges; ++bg_effect)
2,268✔
4070
        {
4071
            auto bge_value = opt_bg_effects[0][bg_effect];
2,187✔
4072
            if (!bge_value)
2,187✔
4073
                continue;
2,153✔
4074
            std::cout << "Your BG Effect: " << passive_bge_names[bg_effect];
34✔
4075
            if (bge_value != -1)
34✔
4076
                std::cout << " " << bge_value;
34✔
4077
            std::cout << std::endl;
2,187✔
4078
        }
4079
        for (const auto &bg_skill : opt_bg_skills[0])
82✔
4080
        {
4081
            std::cout << "Your BG Skill: " << skill_description(all_cards, bg_skill) << std::endl;
1✔
4082
        }
4083

4084
        for (unsigned i(0); i < enemy_decks.size(); ++i)
390✔
4085
        {
4086
            auto enemy_deck = enemy_decks[i];
309✔
4087
            std::cout << "Enemy's Deck:" << enemy_decks_factors[i] << ": "
309✔
4088
                      << (debug_print > 0 ? enemy_deck->long_description() : enemy_deck->medium_description()) << std::endl;
618✔
4089
        }
4090
        for (unsigned bg_effect = PassiveBGE::no_bge; bg_effect < PassiveBGE::num_passive_bges; ++bg_effect)
2,268✔
4091
        {
4092
            auto bge_value = opt_bg_effects[1][bg_effect];
2,187✔
4093
            if (!bge_value)
2,187✔
4094
                continue;
2,153✔
4095
            std::cout << "Enemy's BG Effect: " << passive_bge_names[bg_effect];
34✔
4096
            if (bge_value != -1)
34✔
4097
                std::cout << " " << bge_value;
34✔
4098
            std::cout << std::endl;
2,187✔
4099
        }
4100
        for (const auto &bg_skill : opt_bg_skills[1])
82✔
4101
        {
4102
            std::cout << "Enemy's BG Skill: " << skill_description(all_cards, bg_skill) << std::endl;
1✔
4103
        }
4104
    }
4105
    if (enemy_decks.size() == 1)
81✔
4106
    {
4107
        auto enemy_deck = enemy_decks[0];
43✔
4108
        for (auto x_mult_ss : enemy_deck->effects)
43✔
4109
        {
4110
            if (debug_print >= 0)
×
4111
            {
4112
                std::cout << "Enemy's X-Mult BG Skill (effective X = round_up[X * " << enemy_deck->level << "]): "
×
4113
                          << skill_description(all_cards, x_mult_ss);
×
4114
                if (x_mult_ss.x)
×
4115
                {
4116
                    std::cout << " (eff. X = " << ceil(x_mult_ss.x * enemy_deck->level) << ")";
×
4117
                }
4118
                std::cout << std::endl;
×
4119
            }
4120
            opt_bg_skills[1].push_back({x_mult_ss.id,
×
4121
                                        (unsigned)ceil(x_mult_ss.x * enemy_deck->level),
×
4122
                                        x_mult_ss.y, x_mult_ss.n, x_mult_ss.c,
×
4123
                                        x_mult_ss.s, x_mult_ss.s2, x_mult_ss.all});
×
4124
        }
4125
    }
4126
    auto proc_decks = your_decks;
81✔
4127
    if (opt_multi_optimization)
81✔
4128
    { // only one deck at a time
4129
        proc_decks.erase(proc_decks.begin() + 1, proc_decks.end());
5✔
4130
    }
4131
    Process p(opt_num_threads, all_cards, decks, proc_decks, enemy_decks, factors, gamemode,
81✔
4132
#ifndef NQUEST
4133
              tuo::quest,
4134
#endif
4135
              opt_bg_effects[0], opt_bg_effects[1], opt_bg_skills[0], opt_bg_skills[1]);
81✔
4136

4137
    auto your_deck = your_decks[0];
81✔
4138

4139
    if (!opt_todo.empty())
81✔
4140
    {
4141
    auto op = opt_todo.back();
81✔
4142
    // for (auto op: opt_todo)
4143
    {
81✔
4144
        switch (std::get<2>(op))
81✔
4145
        {
4146
        case noop:
4147
            break;
4148
        case simulate:
76✔
4149
        {
76✔
4150
            EvaluatedResults results = {EvaluatedResults::first_type(enemy_decks.size() * your_decks.size()), 0};
76✔
4151
            results = p.evaluate(std::get<0>(op), results);
76✔
4152
            print_results(results, p.factors);
76✔
4153
            fr = std::make_pair(your_deck->clone(), compute_score(results, p.factors));
76✔
4154
            print_sim_card_values(fr.first, p, std::get<0>(op));
76✔
4155
            break;
76✔
4156
        }
76✔
4157
        case climb_forts:
×
4158
        {
×
4159
            fr = forts_climbing(std::get<0>(op), p);
×
4160
            break;
×
4161
        }
4162
        case climb:
2✔
4163
        {
2✔
4164
            // TODO check for your_decks.size()==1
4165
            fr = hill_climbing(std::get<0>(op), std::get<1>(op), your_decks, p, requirement
2✔
4166
#ifndef NQUEST
4167
                               ,
4168
                               tuo::quest
4169
#endif
4170
            );
2✔
4171
            break;
2✔
4172
        }
4173
        case anneal:
1✔
4174
        {
1✔
4175
            // TODO check for your_decks.size()==1
4176
            fr = simulated_annealing(std::get<0>(op), std::get<1>(op), your_decks, p, requirement
1✔
4177
#ifndef NQUEST
4178
                                     ,
4179
                                     tuo::quest
4180
#endif
4181
            );
1✔
4182
            break;
1✔
4183
        }
4184
        case genetic:
1✔
4185
        {
1✔
4186
            fr = genetic_algorithm(std::get<0>(op), std::get<1>(op), your_decks, p, requirement
1✔
4187
#ifndef NQUEST
4188
                                   ,
4189
                                   tuo::quest
4190
#endif
4191
            );
1✔
4192
            break;
1✔
4193
        }
4194

4195
        case beam:
1✔
4196
        {
1✔
4197
            fr = beam_climb(std::get<0>(op), std::get<1>(op), your_decks, p, requirement
1✔
4198
#ifndef NQUEST
4199
                            ,
4200
                            tuo::quest
4201
#endif
4202
            );
1✔
4203
            break;
1✔
4204
        }
4205
        case reorder:
×
4206
        {
×
4207
            // TODO multi deck mode for reorder
4208
            your_deck->strategy = DeckStrategy::ordered;
×
4209
            use_owned_cards = true;
×
4210
            use_top_level_card = false;
×
4211
            use_top_level_commander = false;
×
4212
            use_owned_dominions = false;
×
4213
            if (min_deck_len == 1 && max_deck_len == 10)
×
4214
            {
4215
                min_deck_len = max_deck_len = your_deck->cards.size();
×
4216
            }
4217
            fund = 0;
×
4218
            debug_print = -1;
×
4219
            owned_cards.clear();
×
4220
            claim_cards({your_deck->commander});
×
4221
            claim_cards(your_deck->cards);
×
4222
            std::vector<Deck *> single_deck = {your_deck};
×
4223
            fr = hill_climbing(std::get<0>(op), std::get<1>(op), single_deck, p, requirement
×
4224
#ifndef NQUEST
4225
                               ,
4226
                               tuo::quest
4227
#endif
4228
            );
×
4229
            break;
×
4230
        }
×
4231
            // fr=nllptr no return
4232
        case debug:
×
4233
        {
×
4234
            ++debug_print;
×
4235
            debug_str.clear();
×
4236
            EvaluatedResults results{EvaluatedResults::first_type(enemy_decks.size()), 0};
×
4237
            results = p.evaluate(1, results);
×
4238
            print_results(results, p.factors);
×
4239
            --debug_print;
×
4240
            break;
×
4241
        }
×
4242
            // fr=nllptr no return
4243
        case debuguntil:
×
4244
        {
×
4245
            ++debug_print;
×
4246
            ++debug_cached;
×
4247
            while (true)
×
4248
            {
4249
                debug_str.clear();
×
4250
                EvaluatedResults results{EvaluatedResults::first_type(enemy_decks.size()), 0};
×
4251
                results = p.evaluate(1, results);
×
4252
                auto score = compute_score(results, p.factors);
×
4253
                if (score.points >= std::get<0>(op) && score.points <= std::get<1>(op))
×
4254
                {
4255
                    std::cout << debug_str << std::flush;
×
4256
                    print_results(results, p.factors);
×
4257
                    break;
×
4258
                }
4259
            }
×
4260
            --debug_cached;
×
4261
            --debug_print;
×
4262
            break;
×
4263
        }
4264
        }
4265
    }
4266
    write_db(prefix);
243✔
4267
    }
4268
    else {
4269
        std::cout << "No operation specified" << std::endl;
×
4270
    }
4271
    return fr;
162✔
4272
}
891✔
4273

4274
std::vector<const char *> strlist(std::vector<std::string> &input)
×
4275
{
4276
    std::vector<const char *> result;
×
4277

4278
    // remember the nullptr terminator
4279
    result.reserve(input.size() + 1);
×
4280

4281
    for (auto &i : input)
×
4282
        result.push_back(i.data());
×
4283
    result.push_back(nullptr);
×
4284
    return result;
×
4285
}
×
4286

4287
DeckResults start(int argc, const char **argv)
×
4288
{
4289
    std::vector<std::string> result_decks;
×
4290
    DeckResults drc;
×
4291
    bool first = true;
×
4292
    result_decks.push_back(argv[1]);
×
4293
    for (int j = 0; j < argc; ++j)
×
4294
    {
4295
        int add_j_inc = 0;
×
4296
        bool do_it = false;
×
4297
        std::stringstream param_file;
×
4298
        if (strcmp(argv[j], "boost-ml") == 0)
×
4299
        {
4300
            do_it = true;
×
4301
            add_j_inc = -1;
×
4302
            param_file << "noop" << std::endl
×
4303
                       << "only-ml" << std::endl
×
4304
                       << "deck @1@ no-ml" << std::endl;
×
4305
        }
4306
        if (strcmp(argv[j], "-p") == 0 || strcmp(argv[j], "params") == 0)
×
4307
        {
4308
            if (j + 1 < argc)
×
4309
            {
4310
                std::ifstream in_param_file(argv[j + 1]);
×
4311
                if (in_param_file.good())
×
4312
                {
4313
                    param_file << in_param_file.rdbuf();
×
4314
                    in_param_file.close();
×
4315
                    do_it = true;
4316
                }
4317
                else
4318
                {
4319
                    std::cout << "Error loading params file " << argv[j + 1] << std::endl;
×
4320
                }
4321
            }
×
4322
            else
4323
            {
4324
                std::cout << "-p/params needs a file as parameter" << std::endl;
×
4325
            }
4326
        }
4327
        if (do_it)
×
4328
        {
4329
            {
×
4330
                param_file.clear();
×
4331
                param_file.seekg(0, std::ios::beg);
×
4332
                // param_file to istream
4333
                std::cout << "Loading params file " << argv[j + 1] << std::endl;
×
4334
                std::string line, tmp, first_line = "";
×
4335
                std::vector<std::string> first_split, cur_split;
×
4336
                // first count parameters in file
4337
                while (param_file && !param_file.eof())
×
4338
                {
4339
                    std::getline(param_file, line);
×
4340
                    if (is_line_empty_or_commented(line))
×
4341
                        continue;
×
4342
                    for (int z = 0; z < argc - j - 1; ++z)
×
4343
                    {
4344
                        std::string rep("$" + std::to_string(z) + "$");
×
4345
                        if (line.find(rep) != std::string::npos && z > add_j_inc)
×
4346
                            add_j_inc = z;
×
4347
                    }
×
4348
                }
4349
                param_file.clear();
×
4350
                param_file.seekg(0, std::ios::beg);
×
4351
                while (param_file && !param_file.eof())
×
4352
                {
4353
                    std::getline(param_file, line);
×
4354
                    if (is_line_empty_or_commented(line))
×
4355
                        continue;
×
4356
                    // std::cout << line << std::endl;
4357
                    if (first_line == "")
×
4358
                    {
4359
                        first_line = line;
×
4360
                        std::istringstream ss(first_line);
×
4361
                        while (ss >> boost::io::quoted(tmp))
×
4362
                        {
4363
                            for (unsigned z = 0; z < result_decks.size(); ++z)
×
4364
                            {
4365
                                boost::replace_all(tmp, "@" + std::to_string(z) + "@", result_decks[z]);
×
4366
                            }
4367
                            for (int z = 0; z < argc - j - 1; ++z)
×
4368
                            {
4369
                                std::string rep("$" + std::to_string(z) + "$");
×
4370
                                boost::replace_all(tmp, rep, std::string(argv[j + 1 + z]));
×
4371
                            }
×
4372
                            first_split.push_back(tmp);
×
4373
                        }
4374
                    }
×
4375
                    else
4376
                    {
4377
                        cur_split = first_split;
×
4378
                        std::istringstream ss(line);
×
4379
                        while (ss >> boost::io::quoted(tmp))
×
4380
                        {
4381
                            for (unsigned z = 0; z < result_decks.size(); ++z)
×
4382
                            {
4383
                                boost::replace_all(tmp, "@" + std::to_string(z) + "@", result_decks[z]);
×
4384
                            }
4385
                            for (int z = 0; z < argc - j - 1; ++z)
×
4386
                            {
4387
                                std::string rep("$" + std::to_string(z) + "$");
×
4388
                                boost::replace_all(tmp, rep, std::string(argv[j + 1 + z]));
×
4389
                            }
×
4390
                            cur_split.push_back(tmp);
×
4391
                        }
4392
                        for (int i = 0; i < argc; ++i)
×
4393
                        {
4394
                            if (i < j)
×
4395
                                cur_split.insert(cur_split.begin() + i, std::string(argv[i]));
×
4396
                            if (i > j + 1 + add_j_inc)
×
4397
                                cur_split.push_back(std::string(argv[i]));
×
4398
                        }
4399
                        if (!first)
×
4400
                        {
4401
                            // replace passed deck with most recent deck
4402
                            cur_split.erase(cur_split.begin() + 1);
×
4403
                            cur_split.insert(cur_split.begin() + 1, result_decks[result_decks.size() - 1]);
×
4404
                        }
4405
                        first = false;
×
4406

4407
                        std::cout << std::endl
×
4408
                                  << "///////////////" << std::endl;
×
4409
                        // std::cout << result_decks[result_decks.size()-1] <<std::endl;
4410
                        int k = 0;
×
4411
                        for (auto &str : cur_split)
×
4412
                        {
4413
                            if (k > 0)
×
4414
                                std::cout << "\"";
×
4415
                            std::cout << str;
×
4416
                            if (k > 0)
×
4417
                                std::cout << "\"";
×
4418
                            std::cout << ' ';
×
4419
                            k++;
×
4420
                        }
4421
                        std::cout << std::endl;
×
4422
                        std::cout << "///////////////" << std::endl
×
4423
                                  << std::endl;
×
4424

4425
                        drc = start(cur_split.size(), strlist(cur_split).data());
×
4426

4427
                        // result to string
4428
                        std::stringstream oss;
×
4429
                        if (drc.first->commander)
×
4430
                            oss << drc.first->commander->m_name << ", ";
×
4431
                        if (drc.first->alpha_dominion)
×
4432
                            oss << drc.first->alpha_dominion->m_name << ", ";
×
4433
                        print_cards_inline(drc.first->cards, oss, drc.first);
×
4434
                        std::string decks(oss.str());
×
4435
                        std::replace(decks.begin(), decks.end(), '\n', ' ');
×
4436
                        result_decks.push_back(decks);
×
4437

4438
                        // print_cards_inline(drc.first->cards,std::cout);
4439
                    }
×
4440
                }
4441
                return drc;
×
4442
            }
×
4443
        }
4444
        else
4445
        {
4446
            // return run(argc,argv);
4447
        }
4448
    }
×
4449
    init();
×
4450
    auto rtrn = run(argc, argv);
×
4451
    return rtrn;
×
4452
}
×
4453

4454
#if !defined(TEST)
4455
int main(int argc, const char **argv)
4456
{
4457
#ifndef NTIMER
4458
    boost::timer::auto_cpu_timer t;
4459
#endif
4460
    if (argc == 2 && strcmp(argv[1], "-version") == 0)
4461
    {
4462
        std::cout << "Tyrant Unleashed Optimizer " << TYRANT_OPTIMIZER_VERSION << std::endl;
4463
        return 0;
4464
    }
4465
    if (argc < 2)
4466
    {
4467
        usage(argc, argv);
4468
        return 255;
4469
    }
4470
    start(argc, argv);
4471
    return 0;
4472
}
4473
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc