Commit a932ce02 authored by sbl1996@126.com's avatar sbl1996@126.com

Support more cards

parent 03b43512
......@@ -55,3 +55,4 @@
03814632
72860663
!side
27204312
......@@ -56,17 +56,7 @@
41999284
41999284
!side
75732622
15397015
15397015
73642296
23434538
5821478
77058170
3679218
25774450
43898403
23002292
23002292
84749824
84749824
73915052
73915053
73915054
73915055
#created by wxapp_ygo
#main
93454062
93454062
93454062
07478431
23434538
23434538
23434538
14558127
14558127
14558127
29942771
29942771
29942771
35726888
92107604
24224830
24299458
94445733
66712905
66712905
68957034
68957034
68957034
30430448
30430448
20618850
20618850
67835547
67835547
93229151
93229151
93229151
31562086
31562086
34813545
34813545
34813545
03734202
03734202
03734202
#extra
55990317
55990317
55990317
28373620
28373620
33198837
42566602
52445243
80666118
87188910
84815190
96633955
66011101
90590303
08728498
!side
70902743
\ No newline at end of file
......@@ -54,7 +54,6 @@
94977269
52687916
73580471
31924889
56832966
84013237
82633039
......
#created by ...
#main
27204311
48452496
48452496
72270339
72270339
72270339
14558127
14558127
14558127
23434538
23434538
23434538
35405755
45663742
9674034
9674034
9674034
90241276
90241276
9742784
12058741
94145021
94145021
97268402
97268402
97268402
2295440
24081957
89023486
89023486
24224830
24224830
26700718
80845034
80845034
80845034
53639887
10045474
10045474
38511382
#extra
84815190
27548199
79606837
50091196
98127546
20665527
45112597
4280258
2772337
61245672
48815792
87871125
27381364
65741786
41999284
!side
27204312
......@@ -57,18 +57,5 @@
08491308
12421694
!side
19613556
12580477
12580477
04031928
04031928
18144506
35269904
08267140
08267140
31849106
31849106
83326048
83326048
15693423
15693423
52340445
27204312
#created by ...
#main
27204311
48452496
66431519
2526224
2526224
2526224
72270339
18621798
14558127
14558127
14558127
23434538
23434538
23434538
9674034
9674034
9674034
90241276
90241276
90241276
90681088
94145021
97268402
97268402
24081957
85106525
85106525
85106525
89023486
24224830
24224830
80845034
80845034
80845034
91703676
65305978
57554544
10045474
10045474
10045474
#extra
93039339
64182380
57134592
20665527
45112597
4280258
2772337
2772337
61245672
8264361
48815792
87871125
29301450
65741786
41999284
!side
27204312
......@@ -57,3 +57,8 @@
32519092
78917791
!side
20001444
27204312
93490857
56495148
14821891
#created by ...
#main
27204311
27204311
27204311
26866984
88284599
51296484
51296484
51296484
14558127
14558127
14558127
92919429
92919429
92919429
23434538
23434538
23434538
25801745
25801745
25801745
4810828
10804018
10774240
10774240
13048472
13048472
13048472
25311006
49238328
49238328
52472775
52472775
24224830
24224830
39114494
98477480
98477480
98477480
10045474
10045474
86310763
#extra
80532587
22850702
22850702
79606837
93039339
98127546
73898890
73898890
9839945
29301450
29301450
29301450
71818935
41999284
94259633
!side
27204312
......@@ -10,6 +10,7 @@
- attribute: 1, int, 0: N/A, same as attribute2str[2:]
- race: 1, int, 0: N/A, same as race2str
- level: 1, int, 0: N/A
- counter: 1, int, 0: N/A
- atk: 2, max 65535 to 2 bytes
- def: 2, max 65535 to 2 bytes
- type: 25, multi-hot, same as type2str
......
......@@ -2,17 +2,11 @@
## Unsupported
- Many (Crossout Designator)
- Blackwing (add_counter)
- Magician (pendulum)
- Shaddoll (add_counter)
- Shiranui (Fairy Tail - Snow)
- Hero (random_selected)
# Messgae
## random_selected
Not supported
## add_counter
Not supported
......@@ -35,14 +29,5 @@ Only 1 attribute is announced at a time.
Not supported:
- DNA Checkup
# Summon
## Tribute Summon
Through `select_tribute` (multi-select)
## Link Summon
Through `select_unselect_card` (select 1 card per time)
## Syncro Summon
- `select_card` to choose the tuner (usually 1 card)
- `select_sum` to choose the non-tuner (1 card per time)
## announce_number
Only 1-12 is supported.
......@@ -454,3 +454,205 @@
13764603
75524093
32519092
89870349
56733747
19324993
18094166
52947044
93347961
75047173
30757127
40044918
21143940
16605586
45906428
59392529
40854197
60461804
9411399
58004362
58481572
1948619
46759931
32828466
90590303
83965310
14124483
27780618
50720316
22865492
89943723
8949584
22908820
24094653
55990317
52445243
93229151
94445733
31562086
28373620
3734202
33198837
92107604
68957034
42566602
29942771
7478431
30430448
66712905
87188910
80666118
20618850
34813545
67835547
93454062
8728498
24299458
66011101
56399890
77058170
27572350
15397015
51452091
73642296
43262273
25774450
8233522
43455065
31849106
34267821
25789292
82385847
70902743
3679218
5821478
83326048
23002292
8267140
4031928
11109820
84749824
5758500
15693423
75732622
19613556
12058741
61245672
80845034
90241276
9742784
72270339
27548199
53639887
38511382
48452496
26700718
65741786
89023486
79606837
20665527
50091196
45112597
9674034
2772337
45663742
24081957
27381364
87871125
48815792
98127546
35405755
4280258
73347079
2009101
81983656
85215458
73652465
58820853
81105204
91351370
82633039
16195942
17377751
27243130
95040215
1475311
53582587
75498415
78156759
52687916
86848580
23338098
49003716
14785765
69031175
59839761
76913983
16051717
72930878
53567095
14087893
33236860
73580471
22835145
5318639
11827244
4939890
3717252
84013237
50907446
48130397
23912837
84433295
19261966
97518132
31924889
74822425
20366274
56832966
77505534
69764158
34710660
6417578
59546797
51023024
94977269
40605147
37445295
4904633
30328508
44394295
77723643
48424886
24635329
91703676
66431519
2526224
18621798
65305978
57554544
90681088
85106525
64182380
8264361
57134592
4810828
51296484
49238328
94259633
13048472
73898890
52472775
25801745
80532587
26866984
9839945
98477480
88284599
92919429
71818935
10774240
39114494
86310763
84211599
10804018
......@@ -71,13 +71,13 @@ class Args:
"""the number of layers for the agent"""
num_channels: int = 128
"""the number of channels for the agent"""
checkpoint: str = "checkpoints/agent.pt"
checkpoint: Optional[str] = "checkpoints/agent.pt"
"""the checkpoint to load"""
embedding_file: Optional[str] = "embeddings_en.npy"
"""the embedding file for card embeddings"""
compile: bool = False
"""if toggled, the model will be compiled"""
optimize: bool = True
"""if toggled, the model will be optimized"""
torch_threads: Optional[int] = None
"""the number of threads to use for torch, defaults to ($OMP_NUM_THREADS or 2) * world_size"""
env_threads: Optional[int] = 16
......@@ -137,24 +137,29 @@ if __name__ == "__main__":
envs = RecordEpisodeStatistics(envs)
if args.agent:
if args.embedding_file:
embeddings = np.load(args.embedding_file)
embedding_shape = embeddings.shape
else:
embedding_shape = None
# count lines of code_list
with open(args.code_list_file, "r") as f:
code_list = f.readlines()
embedding_shape = len(code_list)
L = args.num_layers
agent = Agent(args.num_channels, L, L, 1, embedding_shape).to(device)
agent = agent.eval()
if args.checkpoint:
state_dict = torch.load(args.checkpoint, map_location=device)
else:
state_dict = None
if args.compile:
agent = torch.compile(agent, mode='reduce-overhead')
if state_dict:
agent.load_state_dict(state_dict)
else:
prefix = "_orig_mod."
if state_dict:
state_dict = {k[len(prefix):] if k.startswith(prefix) else k: v for k, v in state_dict.items()}
agent.load_state_dict(state_dict)
if args.optimize:
obs = create_obs(envs.observation_space, (num_envs,), device=device)
with torch.no_grad():
traced_model = torch.jit.trace(agent, (obs,), check_tolerance=False, check_trace=False)
......@@ -220,7 +225,7 @@ if __name__ == "__main__":
winner = 0 if episode_reward * pl > 0 else 1
win = 1 - winner
else:
if episode_reward == -1:
if episode_reward < 0:
win = 0
else:
win = 1
......@@ -230,7 +235,6 @@ if __name__ == "__main__":
win_rates.append(win)
win_reasons.append(1 if win_reason == 1 else 0)
sys.stderr.write(f"Episode {len(episode_lengths)}: length={episode_length}, reward={episode_reward}, win={win}, win_reason={win_reason}\n")
# print(f"Episode {len(episode_lengths)}: length={episode_length}, reward={episode_reward}")
if len(episode_lengths) >= args.num_episodes:
break
......
import os
import time
from dataclasses import dataclass
from typing import Optional
import numpy as np
......@@ -17,7 +18,7 @@ class Args:
"""the directory of ydk files"""
code_list_file: str = "code_list.txt"
"""the file containing the list of card codes"""
embeddings_file: str = "embeddings.npy"
embeddings_file: Optional[str] = "embeddings.npy"
"""the npz file containing the embeddings of the cards"""
cards_db: str = "../assets/locale/en/cards.cdb"
"""the cards database file"""
......@@ -77,6 +78,7 @@ if __name__ == "__main__":
code_list = [int(code.strip()) for code in code_list]
print(f"The database contains {len(code_list)} cards.")
if embeddings_file is not None:
# read embeddings
if not os.path.exists(embeddings_file):
sample_embedding = get_embeddings(["test"])[0]
......@@ -99,17 +101,15 @@ if __name__ == "__main__":
exit()
new_texts = read_texts(cards_db, new_codes)
print(new_texts)
if embeddings_file is not None:
embeddings = get_embeddings(new_texts, args.batch_size, args.wait_time, verbose=True)
# add new embeddings
all_embeddings = np.concatenate([all_embeddings, np.array(embeddings)], axis=0)
np.save(embeddings_file, all_embeddings)
# update code_list
code_list += new_codes
# save embeddings and code_list
np.save(embeddings_file, all_embeddings)
with open(code_list_file, "w") as f:
f.write("\n".join(map(str, code_list)) + "\n")
......
......@@ -45,7 +45,9 @@ class Encoder(nn.Module):
self.bin_intervals = nn.Parameter(bin_intervals, requires_grad=False)
if embedding_shape is None:
n_embed, embed_dim = 1000, 1024
n_embed, embed_dim = 999, 1024
elif isinstance(embedding_shape, int):
n_embed, embed_dim = embedding_shape, 1024
else:
n_embed, embed_dim = embedding_shape
n_embed = 1 + n_embed # 1 (index 0) for unknown
......@@ -55,12 +57,13 @@ class Encoder(nn.Module):
self.id_norm = nn.LayerNorm(c // 4, elementwise_affine=False)
self.owner_embed = nn.Embedding(2, c // 16 * 2)
self.owner_embed = nn.Embedding(2, c // 16)
self.position_embed = nn.Embedding(9, c // 16 * 2)
self.overley_embed = nn.Embedding(2, c // 16)
self.attribute_embed = nn.Embedding(8, c // 16)
self.race_embed = nn.Embedding(27, c // 16)
self.level_embed = nn.Embedding(14, c // 16)
self.counter_embed = nn.Embedding(16, c // 16)
self.type_fc_emb = linear(25, c // 16 * 2)
self.atk_fc_emb = linear(c_num, c // 16)
self.def_fc_emb = linear(c_num, c // 16)
......@@ -98,8 +101,9 @@ class Encoder(nn.Module):
self.a_yesno_embed = nn.Embedding(3, c // divisor)
self.a_phase_embed = nn.Embedding(4, c // divisor)
self.a_cancel_finish_embed = nn.Embedding(3, c // divisor)
self.a_position_embed = nn.Embedding(5, c // divisor)
self.a_option_embed = nn.Embedding(4, c // divisor)
self.a_position_embed = nn.Embedding(9, c // divisor)
self.a_option_embed = nn.Embedding(4, c // divisor // 2)
self.a_number_embed = nn.Embedding(13, c // divisor // 2)
self.a_place_embed = nn.Embedding(31, c // divisor // 2)
self.a_attrib_embed = nn.Embedding(31, c // divisor // 2)
self.a_feat_norm = nn.LayerNorm(c, elementwise_affine=affine)
......@@ -164,9 +168,10 @@ class Encoder(nn.Module):
x_a_cancel = self.a_cancel_finish_embed(x[:, :, 4])
x_a_position = self.a_position_embed(x[:, :, 5])
x_a_option = self.a_option_embed(x[:, :, 6])
x_a_place = self.a_place_embed(x[:, :, 7])
x_a_attrib = self.a_attrib_embed(x[:, :, 8])
return x_a_msg, x_a_act, x_a_yesno, x_a_phase, x_a_cancel, x_a_position, x_a_option, x_a_place, x_a_attrib
x_a_number = self.a_number_embed(x[:, :, 7])
x_a_place = self.a_place_embed(x[:, :, 8])
x_a_attrib = self.a_attrib_embed(x[:, :, 9])
return x_a_msg, x_a_act, x_a_yesno, x_a_phase, x_a_cancel, x_a_position, x_a_option, x_a_number, x_a_place, x_a_attrib
def get_action_card_(self, x, f_cards):
b, n, c = x.shape
......@@ -211,7 +216,8 @@ class Encoder(nn.Module):
x_attribute = self.attribute_embed(x1[:, :, 5])
x_race = self.race_embed(x1[:, :, 6])
x_level = self.level_embed(x1[:, :, 7])
return x_owner, x_position, x_overley, x_attribute, x_race, x_level
x_counter = self.counter_embed(x1[:, :, 8])
return x_owner, x_position, x_overley, x_attribute, x_race, x_level, x_counter
def encode_card_feat2(self, x2):
x_atk = self.num_transform(x2[:, :, 0:2])
......@@ -243,8 +249,8 @@ class Encoder(nn.Module):
x_card_ids = x_cards[:, :, :2].long()
x_card_ids = x_card_ids[..., 0] * 256 + x_card_ids[..., 1]
x_cards_1 = x_cards[:, :, 2:10].long()
x_cards_2 = x_cards[:, :, 10:].to(torch.float32)
x_cards_1 = x_cards[:, :, 2:11].long()
x_cards_2 = x_cards[:, :, 11:].to(torch.float32)
x_id = self.encode_card_id(x_card_ids)
f_loc = self.loc_norm(self.loc_embed(x_cards_1[:, :, 0]))
......
......@@ -7,8 +7,7 @@ from ygoenv.ygopro import init_module
def load_deck(fn):
with open(fn) as f:
lines = f.readlines()
noside = itertools.takewhile(lambda x: "side" not in x, lines)
deck = [int(line) for line in noside if line[:-1].isdigit()]
deck = [int(line) for line in lines if line[:-1].isdigit()]
return deck
......@@ -25,7 +24,7 @@ _languages = {
"chinese": "zh",
}
def init_ygopro(lang, deck, code_list_file, preload_tokens=True):
def init_ygopro(lang, deck, code_list_file, preload_tokens=False):
short = _languages[lang]
db_path = Path(get_root_directory(), 'assets', 'locale', short, 'cards.cdb')
deck_fp = Path(deck)
......
......@@ -2,6 +2,7 @@
#define YGOENV_YGOPRO_YGOPRO_H_
// clang-format off
#include <algorithm>
#include <cstdio>
#include <numeric>
#include <stdexcept>
......@@ -303,7 +304,9 @@ static const ankerl::unordered_dense::map<int, std::string> system_strings = {
{221, "On [%ls], Activate Trigger Effect of [%ls]?"},
{1190, "Add to hand"},
{1192, "Banish"},
{1622, "[%ls] Missed timing"}};
{1621, "Attack Negated"},
{1622, "[%ls] Missed timing"}
};
static std::string get_system_string(int desc) {
auto it = system_strings.find(desc);
......@@ -433,56 +436,55 @@ inline std::string code_to_spec(uint32_t spec_code) {
return ls_to_spec(loc, seq, pos, opponent);
}
static std::vector<uint32> read_main_deck(const std::string &fp) {
static std::tuple<std::vector<uint32>, std::vector<uint32>, std::vector<uint32>> read_decks(const std::string &fp) {
std::ifstream file(fp);
std::string line;
std::vector<uint32> deck;
std::vector<uint32> main_deck, extra_deck, side_deck;
bool found_extra = false;
if (file.is_open()) {
// Read the main deck
while (std::getline(file, line)) {
if ((line.find("side") != std::string::npos) ||
line.find("extra") != std::string::npos) {
if (line.find("side") != std::string::npos) {
break;
}
if (line.find("extra") != std::string::npos) {
found_extra = true;
break;
}
// Check if line contains only digits
if (std::all_of(line.begin(), line.end(), ::isdigit)) {
deck.push_back(std::stoul(line));
}
main_deck.push_back(std::stoul(line));
}
file.close();
} else {
throw std::runtime_error(fmt::format("Unable to open deck file: {}", fp));
}
return deck;
}
static std::vector<uint32> read_extra_deck(const std::string &fp) {
std::ifstream file(fp);
std::string line;
std::vector<uint32> deck;
if (file.is_open()) {
// Read the extra deck
if (found_extra) {
while (std::getline(file, line)) {
if (line.find("extra") != std::string::npos) {
if (line.find("side") != std::string::npos) {
break;
}
// Check if line contains only digits
if (std::all_of(line.begin(), line.end(), ::isdigit)) {
extra_deck.push_back(std::stoul(line));
}
}
}
// Read the side deck
while (std::getline(file, line)) {
if (line.find("side") != std::string::npos) {
break;
}
// Check if line contains only digits
if (std::all_of(line.begin(), line.end(), ::isdigit)) {
deck.push_back(std::stoul(line));
side_deck.push_back(std::stoul(line));
}
}
file.close();
} else {
throw std::runtime_error(fmt::format("Unable to open deck file: {}", fp));
}
return deck;
return std::make_tuple(main_deck, extra_deck, side_deck);
}
template <class K = uint8_t>
......@@ -668,7 +670,7 @@ static const std::vector<int> _msgs = {
MSG_SELECT_TRIBUTE, MSG_SELECT_POSITION, MSG_SELECT_EFFECTYN,
MSG_SELECT_YESNO, MSG_SELECT_BATTLECMD, MSG_SELECT_UNSELECT_CARD,
MSG_SELECT_OPTION, MSG_SELECT_PLACE, MSG_SELECT_SUM,
MSG_SELECT_DISFIELD, MSG_ANNOUNCE_ATTRIB,
MSG_SELECT_DISFIELD, MSG_ANNOUNCE_ATTRIB, MSG_ANNOUNCE_NUMBER,
};
static const ankerl::unordered_dense::map<int, uint8_t> msg2id =
......@@ -726,6 +728,15 @@ static std::vector<int> find_substrs(const std::string &str,
return res;
}
inline std::string time_now() {
// strftime %Y-%m-%d %H-%M-%S
time_t now = time(0);
tm *ltm = localtime(&now);
char buffer[80];
strftime(buffer, 80, "%Y-%m-%d %H-%M-%S", ltm);
return std::string(buffer);
}
// from ygopro/gframe/replay.h
// replay flag
......@@ -762,7 +773,7 @@ class Card {
friend class YGOProEnv;
protected:
CardCode code_;
CardCode code_ = 0;
uint32_t alias_;
uint64_t setcode_;
uint32_t type_;
......@@ -785,6 +796,7 @@ protected:
uint32_t location_ = 0;
uint32_t sequence_ = 0;
uint32_t position_ = 0;
uint32_t counter_ = 0;
public:
Card() = default;
......@@ -872,7 +884,7 @@ inline Card db_query_card(const SQLite::Database &db, CardCode code) {
query1.bind(1, code);
bool found = query1.executeStep();
if (!found) {
std::string msg = "Card not found: " + std::to_string(code);
std::string msg = "[db_query_card] Card not found: " + std::to_string(code);
throw std::runtime_error(msg);
}
......@@ -1020,7 +1032,7 @@ inline void preload_deck(const SQLite::Database &db,
inline uint32 card_reader_callback(CardCode code, card_data *card) {
auto it = cards_data_.find(code);
if (it == cards_data_.end()) {
throw std::runtime_error("Card not found: " + std::to_string(code));
throw std::runtime_error("[card_reader_callback] Card not found: " + std::to_string(code));
}
*card = it->second;
return 0;
......@@ -1077,8 +1089,7 @@ static void init_module(const std::string &db_path,
SQLite::Database db(db_path, SQLite::OPEN_READONLY);
for (const auto &[name, deck] : decks) {
std::vector<CardCode> main_deck = read_main_deck(deck);
std::vector<CardCode> extra_deck = read_extra_deck(deck);
auto [main_deck, extra_deck, side_deck] = read_decks(deck);
main_decks_[name] = main_deck;
extra_decks_[name] = extra_deck;
if (name[0] != '_') {
......@@ -1087,6 +1098,7 @@ static void init_module(const std::string &db_path,
preload_deck(db, main_deck);
preload_deck(db, extra_deck);
preload_deck(db, side_deck);
}
for (auto &[name, deck] : extra_decks_) {
......@@ -1215,9 +1227,9 @@ public:
}
template <typename Config>
static decltype(auto) StateSpec(const Config &conf) {
int n_action_feats = 9 + conf["max_multi_select"_] * 2;
int n_action_feats = 10 + conf["max_multi_select"_] * 2;
return MakeDict(
"obs:cards_"_.Bind(Spec<uint8_t>({conf["max_cards"_] * 2, 39})),
"obs:cards_"_.Bind(Spec<uint8_t>({conf["max_cards"_] * 2, 40})),
"obs:global_"_.Bind(Spec<uint8_t>({9})),
"obs:actions_"_.Bind(
Spec<uint8_t>({conf["max_options"_], n_action_feats})),
......@@ -1282,6 +1294,9 @@ protected:
std::vector<uint32> extra_deck0_;
std::vector<uint32> extra_deck1_;
std::string deck_name_[2] = {"", ""};
std::string nickname_[2] = {"Alice", "Bob"};
const std::vector<PlayMode> play_modes_;
// if play_mode_ == 'bot' or 'human', player_ is the order of the ai player
......@@ -1391,9 +1406,6 @@ public:
}
~YGOProEnv() {
if (record_) {
// delete[] replay_data_;
}
for (int i = 0; i < 2; i++) {
if (players_[i] != nullptr) {
delete players_[i];
......@@ -1430,23 +1442,6 @@ public:
}
}
if (record_) {
if (is_recording && fp_ != nullptr) {
fclose(fp_);
}
auto now = std::chrono::system_clock::now().time_since_epoch();
auto now_seconds =
std::chrono::duration_cast<std::chrono::seconds>(now).count();
auto fname = fmt::format("./replay/{}.yrp", now_seconds);
fp_ = fopen(fname.c_str(), "wb");
if (!fp_) {
throw std::runtime_error("Failed to open file for replay: " + fname);
}
// rdata_ = replay_data_;
is_recording = true;
}
turn_count_ = 0;
history_actions_0_.Zero();
......@@ -1460,25 +1455,96 @@ public:
pduel_ = OCG_CreateDuel(duel_seed);
ulock.unlock();
int init_lp = 8000;
int startcount = 5;
int drawcount = 1;
for (PlayerId i = 0; i < 2; i++) {
if (players_[i] != nullptr) {
delete players_[i];
}
std::string nickname = i == 0 ? "Alice" : "Bob";
int init_lp = 8000;
if (i == ai_player_) {
nickname = "Agent";
}
nickname_[i] = nickname;
if ((play_mode_ == kHuman) && (i != ai_player_)) {
players_[i] = new HumanPlayer(nickname, init_lp, i, verbose_);
players_[i] = new HumanPlayer(nickname_[i], init_lp, i, verbose_);
} else if (play_mode_ == kRandomBot) {
players_[i] = new RandomAI(max_options(), dist_int_(gen_), nickname,
players_[i] = new RandomAI(max_options(), dist_int_(gen_), nickname_[i],
init_lp, i, verbose_);
} else {
players_[i] = new GreedyAI(nickname, init_lp, i, verbose_);
players_[i] = new GreedyAI(nickname_[i], init_lp, i, verbose_);
}
OCG_SetPlayerInfo(pduel_, i, init_lp, 5, 1);
OCG_SetPlayerInfo(pduel_, i, init_lp, startcount, drawcount);
load_deck(i);
lp_[i] = players_[i]->init_lp_;
}
if (record_) {
if (is_recording && fp_ != nullptr) {
fclose(fp_);
}
auto time_str = time_now();
// Use last 4 digits of seed as unique id
auto seed_ = duel_seed % 10000;
std::string fname;
while (true) {
fname = fmt::format("./replay/a{} {:04d}.yrp", time_str, seed_);
// check existence
if (std::filesystem::exists(fname)) {
seed_ = (seed_ + 1) % 10000;
} else {
break;
}
}
fp_ = fopen(fname.c_str(), "wb");
if (!fp_) {
throw std::runtime_error("Failed to open file for replay: " + fname);
}
is_recording = true;
ReplayHeader rh;
rh.id = 0x31707279;
rh.version = 0x00001360;
rh.flag = REPLAY_UNIFORM;
rh.seed = duel_seed;
rh.start_time = (unsigned int)time(nullptr);
fwrite(&rh, sizeof(rh), 1, fp_);
for (PlayerId i = 0; i < 2; i++) {
uint16_t name[20];
memset(name, 0, 40);
std::string name_str = fmt::format("{} {}", nickname_[i], deck_name_[i]);
if (name_str.size() > 20) {
// truncate
name_str = name_str.substr(0, 20);
}
fmt::println("name: {}", name_str);
str_to_uint16(name_str.c_str(), name);
fwrite(name, 40, 1, fp_);
}
ReplayWriteInt32(init_lp);
ReplayWriteInt32(startcount);
ReplayWriteInt32(drawcount);
ReplayWriteInt32(duel_options_);
for (PlayerId i = 0; i < 2; i++) {
auto &main_deck = i == 0 ? main_deck0_ : main_deck1_;
auto &extra_deck = i == 0 ? extra_deck0_ : extra_deck1_;
ReplayWriteInt32(main_deck.size());
for (auto code : main_deck) {
ReplayWriteInt32(code);
}
ReplayWriteInt32(extra_deck.size());
for (int i = extra_deck.size() - 1; i >= 0; --i) {
ReplayWriteInt32(extra_deck[i]);
}
}
}
OCG_StartDuel(pduel_, duel_options_);
duel_started_ = true;
winner_ = 255;
......@@ -1581,11 +1647,13 @@ public:
float base_reward = 1.0;
int win_turn = turn_count_ - winner_;
if (win_turn <= 1) {
base_reward = 4.0;
base_reward = 8.0;
} else if (win_turn <= 3) {
base_reward = 3.0;
base_reward = 4.0;
} else if (win_turn <= 5) {
base_reward = 2.0;
} else {
base_reward = 0.5 + 1.0 / (win_turn - 5);
}
if (play_mode_ == kSelfPlay) {
// to_play_ is the previous player
......@@ -1707,17 +1775,18 @@ private:
f_cards(offset, 7) = attribute2id.at(c.attribute_);
f_cards(offset, 8) = race2id.at(c.race_);
f_cards(offset, 9) = c.level_;
f_cards(offset, 10) = std::min(c.counter_, static_cast<uint32_t>(15));
auto [atk1, atk2] = float_transform(c.attack_);
f_cards(offset, 10) = atk1;
f_cards(offset, 11) = atk2;
f_cards(offset, 11) = atk1;
f_cards(offset, 12) = atk2;
auto [def1, def2] = float_transform(c.defense_);
f_cards(offset, 12) = def1;
f_cards(offset, 13) = def2;
f_cards(offset, 13) = def1;
f_cards(offset, 14) = def2;
auto type_ids = type_to_ids(c.type_);
for (int j = 0; j < type_ids.size(); ++j) {
f_cards(offset, 14 + j) = type_ids[j];
f_cards(offset, 15 + j) = type_ids[j];
}
}
}
......@@ -1784,13 +1853,17 @@ private:
feat(i, _obs_action_feat_offset() + 6) = option - '0';
}
void _set_obs_action_number(TArray<uint8_t> &feat, int i, char number) {
feat(i, _obs_action_feat_offset() + 7) = number - '0';
}
void _set_obs_action_place(TArray<uint8_t> &feat, int i,
const std::string &spec) {
feat(i, _obs_action_feat_offset() + 7) = cmd_place2id.at(spec);
feat(i, _obs_action_feat_offset() + 8) = cmd_place2id.at(spec);
}
void _set_obs_action_attrib(TArray<uint8_t> &feat, int i, uint8_t attrib) {
feat(i, _obs_action_feat_offset() + 8) = attribute2id.at(attrib);
feat(i, _obs_action_feat_offset() + 9) = attribute2id.at(attrib);
}
void _set_obs_action(TArray<uint8_t> &feat, int i, int msg,
......@@ -1882,6 +1955,8 @@ private:
_set_obs_action_place(feat, i, option);
} else if (msg == MSG_ANNOUNCE_ATTRIB) {
_set_obs_action_attrib(feat, i, 1 << (option[0] - '1'));
} else if (msg == MSG_ANNOUNCE_NUMBER) {
_set_obs_action_number(feat, i, option[0]);
} else {
throw std::runtime_error("Unsupported message " + std::to_string(msg));
}
......@@ -1968,47 +2043,15 @@ private:
// ygopro-core API
intptr_t OCG_CreateDuel(uint32_t seed) {
if (record_) {
ReplayHeader rh;
rh.id = 0x31707279;
rh.version = 0x00001360;
rh.flag = REPLAY_UNIFORM;
rh.seed = seed;
rh.start_time = (unsigned int)time(nullptr);
fwrite(&rh, sizeof(rh), 1, fp_);
fflush(fp_);
}
std::mt19937 rnd(seed);
return create_duel(rnd());
}
void OCG_SetPlayerInfo(intptr_t pduel, int32 playerid, int32 lp, int32 startcount, int32 drawcount) {
if (record_ && playerid == 0) {
{
uint16_t name[20];
memset(name, 0, 40);
str_to_uint16("Alice", name);
fwrite(name, 40, 1, fp_);
}
{
uint16_t name[20];
memset(name, 0, 40);
str_to_uint16("Bob", name);
fwrite(name, 40, 1, fp_);
}
ReplayWriteInt32(lp);
ReplayWriteInt32(startcount);
ReplayWriteInt32(drawcount);
ReplayWriteInt32(duel_options_);
}
set_player_info(pduel, playerid, lp, startcount, drawcount);
}
void OCG_NewCard(intptr_t pduel, uint32 code, uint8 owner, uint8 playerid, uint8 location, uint8 sequence, uint8 position) {
if (record_) {
ReplayWriteInt32(code);
}
new_card(pduel, code, owner, playerid, location, sequence, position);
}
......@@ -2155,31 +2198,32 @@ private:
// generate random deck name
std::uniform_int_distribution<uint64_t> dist_int(0,
deck_names_.size() - 1);
deck = deck_names_[dist_int(gen_)];
deck_name_[player] = deck_names_[dist_int(gen_)];
} else {
deck_name_[player] = deck;
}
deck = deck_name_[player];
main_deck = main_decks_.at(deck);
extra_deck = extra_decks_.at(deck);
if (verbose_) {
fmt::println("{} {}: {}, main({}), extra({})", player, nickname_[player],
deck, main_deck.size(), extra_deck.size());
}
if (shuffle) {
std::shuffle(main_deck.begin(), main_deck.end(), gen_);
}
// add main deck in reverse order following ygopro
// but since we have shuffled deck, so just add in order
if (record_) {
ReplayWriteInt32(main_deck.size());
}
for (int i = 0; i < main_deck.size(); i++) {
OCG_NewCard(pduel_, main_deck[i], player, player, LOCATION_DECK, 0,
POS_FACEDOWN_DEFENSE);
}
if (record_) {
ReplayWriteInt32(extra_deck.size());
}
// add extra deck in reverse order following ygopro
for (int i = extra_deck.size() - 1; i >= 0; --i) {
OCG_NewCard(pduel_, extra_deck[i], player, player, LOCATION_EXTRA, 0,
......@@ -2261,7 +2305,7 @@ private:
int32_t bl = OCG_QueryCard(pduel_, player, loc, seq, flags, query_buf_, 0);
qdp_ = 0;
if (bl <= 0) {
throw std::runtime_error("Invalid card");
throw std::runtime_error("[get_card_code] Invalid card");
}
qdp_ += 8;
return q_read_u32();
......@@ -2274,11 +2318,11 @@ private:
int32_t bl = OCG_QueryCard(pduel_, player, loc, seq, flags, query_buf_, 0);
qdp_ = 0;
if (bl <= 0) {
throw std::runtime_error("Invalid card");
throw std::runtime_error("[get_card] Invalid card (bl <= 0)");
}
uint32_t f = q_read_u32();
if (f == LEN_EMPTY) {
throw std::runtime_error("Invalid card");
return Card();
}
f = q_read_u32();
CardCode code = q_read_u32();
......@@ -2368,8 +2412,13 @@ private:
// TODO: counters
uint32_t n_counters = q_read_u32();
for (int i = 0; i < n_counters; ++i) {
if (i == 0) {
c.counter_ = q_read_u32();
}
else {
q_read_u32();
}
}
c.lscale_ = q_read_u32();
c.rscale_ = q_read_u32();
......@@ -2711,6 +2760,9 @@ private:
uint8_t type = read_u8();
uint32_t value = read_u32();
Card card = get_card(player, loc, seq);
if (card.code_ == 0) {
return;
}
if (type == CHINT_RACE) {
std::string races_str = "TODO";
for (PlayerId pl = 0; pl < 2; pl++) {
......@@ -2797,6 +2849,43 @@ private:
p->notify(fmt::format("{}: {}", i + 1, cards[i].name_));
}
}
} else if (msg_ == MSG_RANDOM_SELECTED) {
if (!verbose_) {
dp_ = dl_;
return;
}
auto player = read_u8();
auto count = read_u8();
std::vector<Card> cards;
for (int i = 0; i < count; ++i) {
auto c = read_u8();
auto loc = read_u8();
if (loc & LOCATION_OVERLAY) {
throw std::runtime_error("Overlay not supported for random selected");
}
auto seq = read_u8();
auto pos = read_u8();
cards.push_back(get_card(c, loc, seq));
}
for (PlayerId pl = 0; pl < 2; pl++) {
auto p = players_[pl];
auto s = "card is";
if (count > 1) {
s = "cards are";
}
if (pl == player) {
p->notify(fmt::format("Your {} {} randomly selected:", s, count));
} else {
p->notify(fmt::format("{}'s {} {} randomly selected:",
players_[player]->nickname_, s, count));
}
for (int i = 0; i < count; ++i) {
p->notify(fmt::format("{}: {}", cards[i].get_spec(pl), cards[i].name_));
}
}
} else if (msg_ == MSG_CONFIRM_CARDS) {
auto player = read_u8();
auto size = read_u8();
......@@ -2898,6 +2987,47 @@ private:
// }
// OCG_SetResponseb(pduel_, resp_buf_);
// };
} else if (msg_ == MSG_ADD_COUNTER) {
if (!verbose_) {
dp_ = dl_;
return;
}
auto ctype = read_u16();
auto player = read_u8();
auto loc = read_u8();
auto seq = read_u8();
auto count = read_u16();
auto c = get_card(player, loc, seq);
auto pl = players_[player];
PlayerId op_id = 1 - player;
auto op = players_[op_id];
// TODO: counter type to string
pl->notify(fmt::format("{} counter(s) of type {} placed on {} ().", count, "UNK", c.name_, c.get_spec(player)));
op->notify(fmt::format("{} counter(s) of type {} placed on {} ().", count, "UNK", c.name_, c.get_spec(op_id)));
} else if (msg_ == MSG_REMOVE_COUNTER) {
if (!verbose_) {
dp_ = dl_;
return;
}
auto ctype = read_u16();
auto player = read_u8();
auto loc = read_u8();
auto seq = read_u8();
auto count = read_u16();
auto c = get_card(player, loc, seq);
auto pl = players_[player];
PlayerId op_id = 1 - player;
auto op = players_[op_id];
pl->notify(fmt::format("{} counter(s) of type {} removed from {} ().", count, "UNK", c.name_, c.get_spec(player)));
op->notify(fmt::format("{} counter(s) of type {} removed from {} ().", count, "UNK", c.name_, c.get_spec(op_id)));
} else if (msg_ == MSG_ATTACK_DISABLED) {
if (!verbose_) {
dp_ = dl_;
return;
}
for (PlayerId pl = 0; pl < 2; pl++) {
players_[pl]->notify(get_system_string(1621));
}
} else if (msg_ == MSG_SHUFFLE_SET_CARD) {
if (!verbose_) {
dp_ = dl_;
......@@ -2915,6 +3045,20 @@ private:
auto op = players_[1 - player];
pl->notify("You shuffled your deck.");
op->notify(pl->nickname_ + " shuffled their deck.");
} else if (msg_ == MSG_SHUFFLE_EXTRA) {
if (!verbose_) {
dp_ = dl_;
return;
}
auto player = read_u8();
auto count = read_u8();
for (int i = 0; i < count; ++i) {
read_u32();
}
auto pl = players_[player];
auto op = players_[1 - player];
pl->notify(fmt::format("You shuffled your extra deck ({}).", count));
op->notify(fmt::format("{} shuffled their extra deck ({}).", pl->nickname_, count));
} else if (msg_ == MSG_SHUFFLE_HAND) {
if (!verbose_) {
dp_ = dl_;
......@@ -3809,10 +3953,22 @@ private:
throw std::runtime_error("Unknown effectyn desc " +
std::to_string(desc) + " of " + name);
}
} else if (desc < 10000u) {
s = get_system_string(desc);
} else {
CardCode code = (desc >> 4) & 0x0fffffff;
uint32_t offset = desc & 0xf;
if (cards_.find(code) != cards_.end()) {
auto &card_ = c_get_card(code);
s = card_.strings_[offset];
if (s.empty()) {
s = "???";
}
} else {
throw std::runtime_error("Unknown effectyn desc " +
std::to_string(desc) + " of " + name);
}
}
pl->notify(s);
pl->notify("Please enter y or n.");
} else {
......@@ -4083,6 +4239,35 @@ private:
resp_buf_[2] = seq;
OCG_SetResponseb(pduel_, resp_buf_);
};
} else if (msg_ == MSG_ANNOUNCE_NUMBER) {
auto player = read_u8();
to_play_ = player;
auto count = read_u8();
std::vector<int> numbers;
for (int i = 0; i < count; ++i) {
int number = read_u32();
if (number <= 0 || number > 12) {
throw std::runtime_error("Number " + std::to_string(number) +
" not implemented for announce number");
}
numbers.push_back(number);
options_.push_back(std::string(1, '0' + number));
}
if (verbose_) {
auto pl = players_[player];
std::string str = "Select a number, one of: [";
for (int i = 0; i < count; ++i) {
str += std::to_string(numbers[i]);
if (i < count - 1) {
str += ", ";
}
}
str += "]";
pl->notify(str);
}
callback_ = [this](int idx) {
OCG_SetResponsei(pduel_, idx);
};
} else if (msg_ == MSG_ANNOUNCE_ATTRIB) {
auto player = read_u8();
to_play_ = player;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment