@slot-engine/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +33 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +7 -0
- package/README.md +8 -0
- package/dist/index.d.mts +1306 -0
- package/dist/index.d.ts +1306 -0
- package/dist/index.js +2929 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2874 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/zstd.exe +0 -0
- package/dist/optimizer-rust/Cargo.toml +19 -0
- package/dist/optimizer-rust/src/exes.rs +154 -0
- package/dist/optimizer-rust/src/main.rs +1659 -0
- package/index.ts +205 -0
- package/lib/zstd.exe +0 -0
- package/optimizer-rust/Cargo.toml +19 -0
- package/optimizer-rust/src/exes.rs +154 -0
- package/optimizer-rust/src/main.rs +1659 -0
- package/package.json +33 -0
- package/src/Board.ts +527 -0
- package/src/Book.ts +83 -0
- package/src/GameConfig.ts +148 -0
- package/src/GameMode.ts +86 -0
- package/src/GameState.ts +272 -0
- package/src/GameSymbol.ts +61 -0
- package/src/ReelGenerator.ts +589 -0
- package/src/ResultSet.ts +207 -0
- package/src/Simulation.ts +625 -0
- package/src/SlotGame.ts +117 -0
- package/src/Wallet.ts +203 -0
- package/src/WinType.ts +102 -0
- package/src/analysis/index.ts +198 -0
- package/src/analysis/utils.ts +128 -0
- package/src/optimizer/OptimizationConditions.ts +99 -0
- package/src/optimizer/OptimizationParameters.ts +46 -0
- package/src/optimizer/OptimizationScaling.ts +18 -0
- package/src/optimizer/index.ts +142 -0
- package/src/utils/math-config.ts +109 -0
- package/src/utils/setup-file.ts +36 -0
- package/src/utils/zstd.ts +28 -0
- package/src/winTypes/ClusterWinType.ts +3 -0
- package/src/winTypes/LinesWinType.ts +208 -0
- package/src/winTypes/ManywaysWinType.ts +3 -0
- package/tsconfig.json +19 -0
- package/utils.ts +270 -0
|
@@ -0,0 +1,1659 @@
|
|
|
1
|
+
use exes::IdentityCondition;
|
|
2
|
+
use ndarray::{s, Array1};
|
|
3
|
+
use rand::prelude::*;
|
|
4
|
+
use rand::Rng;
|
|
5
|
+
use rand_distr::{Distribution, WeightedIndex};
|
|
6
|
+
use rayon::prelude::*;
|
|
7
|
+
use std::env;
|
|
8
|
+
use std::hash::{Hash, Hasher};
|
|
9
|
+
use std::mem;
|
|
10
|
+
use std::path::{Path};
|
|
11
|
+
use std::{
|
|
12
|
+
cmp::Ordering, collections::HashMap, fs, fs::File, io::BufWriter, io::Write,
|
|
13
|
+
time::Instant,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
mod exes;
|
|
17
|
+
use exes::{
|
|
18
|
+
load_config_data, load_force_options, read_look_up_table, DressJson, FenceJson,
|
|
19
|
+
LookUpTableEntry, SearchResult
|
|
20
|
+
}; // Import the functions
|
|
21
|
+
// use flame;
|
|
22
|
+
|
|
23
|
+
fn main() {
|
|
24
|
+
// Read the contents of the file
|
|
25
|
+
let now = Instant::now();
|
|
26
|
+
let args: Vec<String> = env::args().collect();
|
|
27
|
+
let contents: String;
|
|
28
|
+
if args.len() > 1 {
|
|
29
|
+
contents = fs::read_to_string(args[1].clone()).expect("Failed to read file");
|
|
30
|
+
} else {
|
|
31
|
+
contents = fs::read_to_string("src/setup.txt").expect("Failed to read file");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let mut variables: HashMap<String, String> = HashMap::new();
|
|
35
|
+
for line in contents.lines() {
|
|
36
|
+
let parts: Vec<&str> = line.split(';').collect();
|
|
37
|
+
if parts.len() == 2 {
|
|
38
|
+
variables.insert(parts[0].to_string(), parts[1].to_string());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let game_name = variables
|
|
43
|
+
.get("game_name")
|
|
44
|
+
.unwrap_or(&"".to_string())
|
|
45
|
+
.to_string();
|
|
46
|
+
let bet_type = variables
|
|
47
|
+
.get("bet_type")
|
|
48
|
+
.unwrap_or(&"".to_string())
|
|
49
|
+
.to_string();
|
|
50
|
+
let num_show_pigs = variables
|
|
51
|
+
.get("num_show_pigs")
|
|
52
|
+
.unwrap_or(&"0".to_string())
|
|
53
|
+
.parse::<u32>()
|
|
54
|
+
.unwrap_or(0);
|
|
55
|
+
let num_pigs_per_fence = variables
|
|
56
|
+
.get("num_pigs_per_fence")
|
|
57
|
+
.unwrap_or(&"0".to_string())
|
|
58
|
+
.parse::<u32>()
|
|
59
|
+
.unwrap_or(0);
|
|
60
|
+
let threads_for_fence_construction = variables
|
|
61
|
+
.get("threads_for_fence_construction")
|
|
62
|
+
.unwrap_or(&"0".to_string())
|
|
63
|
+
.parse::<u32>()
|
|
64
|
+
.unwrap_or(0);
|
|
65
|
+
let threads_for_show_construction = variables
|
|
66
|
+
.get("threads_for_show_construction")
|
|
67
|
+
.unwrap_or(&"0".to_string())
|
|
68
|
+
.parse::<u32>()
|
|
69
|
+
.unwrap_or(0);
|
|
70
|
+
|
|
71
|
+
let test_spins: Vec<u32> = variables
|
|
72
|
+
.get("test_spins")
|
|
73
|
+
.map(|s| {
|
|
74
|
+
s.trim_matches(|c| c == '[' || c == ']')
|
|
75
|
+
.split(',')
|
|
76
|
+
.map(|v| v.trim().parse::<u32>().unwrap_or(0))
|
|
77
|
+
.collect()
|
|
78
|
+
})
|
|
79
|
+
.unwrap_or(Vec::new());
|
|
80
|
+
|
|
81
|
+
let test_spins_weights: Vec<f64> = variables
|
|
82
|
+
.get("test_spins_weights")
|
|
83
|
+
.map(|s| {
|
|
84
|
+
s.trim_matches(|c| c == '[' || c == ']')
|
|
85
|
+
.split(',')
|
|
86
|
+
.map(|v| v.trim().parse::<f64>().unwrap_or(0.0))
|
|
87
|
+
.collect()
|
|
88
|
+
})
|
|
89
|
+
.unwrap_or(Vec::new());
|
|
90
|
+
|
|
91
|
+
let simulation_trials = variables
|
|
92
|
+
.get("simulation_trials")
|
|
93
|
+
.unwrap_or(&"0".to_string())
|
|
94
|
+
.parse::<u32>()
|
|
95
|
+
.unwrap_or(0);
|
|
96
|
+
|
|
97
|
+
let graph_indexes: Vec<u32> = variables
|
|
98
|
+
.get("graph_indexes")
|
|
99
|
+
.map(|s| {
|
|
100
|
+
s.trim_matches(|c| c == '[' || c == ']')
|
|
101
|
+
.split(',')
|
|
102
|
+
.map(|v| v.parse::<u32>().unwrap_or(0))
|
|
103
|
+
.collect()
|
|
104
|
+
})
|
|
105
|
+
.unwrap_or(Vec::new());
|
|
106
|
+
|
|
107
|
+
let run_1000_batch = variables
|
|
108
|
+
.get("run_1000_batch")
|
|
109
|
+
.unwrap_or(&"false".to_string())
|
|
110
|
+
.parse::<bool>()
|
|
111
|
+
.unwrap_or(false);
|
|
112
|
+
let user_game_build_path = variables
|
|
113
|
+
.get("user_game_build_path")
|
|
114
|
+
.unwrap_or(&"".to_string())
|
|
115
|
+
.to_string();
|
|
116
|
+
let min_mean_to_median = variables
|
|
117
|
+
.get("min_mean_to_median")
|
|
118
|
+
.unwrap_or(&"".to_string())
|
|
119
|
+
.parse::<f64>()
|
|
120
|
+
.unwrap_or(2.5);
|
|
121
|
+
let max_mean_to_median = variables
|
|
122
|
+
.get("max_mean_to_median")
|
|
123
|
+
.unwrap_or(&"".to_string())
|
|
124
|
+
.parse::<f64>()
|
|
125
|
+
.unwrap_or(3.0);
|
|
126
|
+
let pmb_rtp = variables
|
|
127
|
+
.get("pmb_rtp")
|
|
128
|
+
.unwrap_or(&"".to_string())
|
|
129
|
+
.parse::<f64>()
|
|
130
|
+
.unwrap_or(1.0);
|
|
131
|
+
|
|
132
|
+
run_farm(
|
|
133
|
+
&game_name,
|
|
134
|
+
&bet_type,
|
|
135
|
+
num_show_pigs,
|
|
136
|
+
num_pigs_per_fence,
|
|
137
|
+
threads_for_fence_construction,
|
|
138
|
+
threads_for_show_construction,
|
|
139
|
+
&test_spins,
|
|
140
|
+
&test_spins_weights,
|
|
141
|
+
simulation_trials,
|
|
142
|
+
&graph_indexes,
|
|
143
|
+
run_1000_batch,
|
|
144
|
+
&user_game_build_path,
|
|
145
|
+
min_mean_to_median,
|
|
146
|
+
max_mean_to_median,
|
|
147
|
+
pmb_rtp,
|
|
148
|
+
);
|
|
149
|
+
println!("time taken {}ms", now.elapsed().as_millis());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn run_farm(
|
|
153
|
+
game_name: &str,
|
|
154
|
+
bet_type: &str,
|
|
155
|
+
num_show_pigs: u32,
|
|
156
|
+
num_pigs_per_fence: u32,
|
|
157
|
+
threads_for_fence_construction: u32,
|
|
158
|
+
threads_for_show_construction: u32,
|
|
159
|
+
test_spins: &Vec<u32>,
|
|
160
|
+
test_spins_weights: &Vec<f64>,
|
|
161
|
+
simulation_trials: u32,
|
|
162
|
+
graph_indexes: &[u32],
|
|
163
|
+
run_1000_batch: bool,
|
|
164
|
+
user_game_build_path: &str,
|
|
165
|
+
min_mean_to_median: f64,
|
|
166
|
+
max_mean_to_median: f64,
|
|
167
|
+
pmb_rtp: f64,
|
|
168
|
+
) {
|
|
169
|
+
println!("Running Simulations: {} - Mode: {}", game_name, bet_type);
|
|
170
|
+
|
|
171
|
+
// LOAD IN FORCE OPTIONS AND CONFIG FILE
|
|
172
|
+
let force_options = load_force_options(game_name, bet_type, user_game_build_path.to_string());
|
|
173
|
+
|
|
174
|
+
let config_file: exes::ConfigData;
|
|
175
|
+
config_file = load_config_data(game_name, user_game_build_path.to_string());
|
|
176
|
+
// NEED TO PULL OUT THE INDEXES OF BET MODE AND DRESSES
|
|
177
|
+
let mut bet_mode_index = 0;
|
|
178
|
+
let mut dress_index = 0;
|
|
179
|
+
let mut fence_index = 0;
|
|
180
|
+
while config_file.bet_modes[bet_mode_index].bet_mode != bet_type {
|
|
181
|
+
bet_mode_index += 1;
|
|
182
|
+
}
|
|
183
|
+
while config_file.dresses[dress_index].bet_mode != bet_type {
|
|
184
|
+
dress_index += 1;
|
|
185
|
+
}
|
|
186
|
+
while config_file.fences[fence_index].bet_mode != bet_type {
|
|
187
|
+
fence_index += 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
////////////////////
|
|
191
|
+
// DECLARE VARIABLES
|
|
192
|
+
////////////////////
|
|
193
|
+
let mut lookup_table = match read_look_up_table(game_name, bet_type, user_game_build_path.to_string())
|
|
194
|
+
{
|
|
195
|
+
Ok(table) => table,
|
|
196
|
+
Err(err) => {
|
|
197
|
+
eprintln!("Error reading the CSV file: {}", err);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
// NOW WE WANT TO GET A VECTOR CONTAINING ALL THE SORTED WINS
|
|
202
|
+
let mut sorted_wins: Vec<f64> = Vec::new();
|
|
203
|
+
let bet_amount = config_file.bet_modes[bet_mode_index].cost;
|
|
204
|
+
let rtp = config_file.bet_modes[bet_mode_index].rtp;
|
|
205
|
+
let mut total_prob = 0.0;
|
|
206
|
+
let mut fences: Vec<Fence> = Vec::new();
|
|
207
|
+
|
|
208
|
+
// FIRST THING WE HAVE TO DO IS CREATE EACH FENCE FOR PIGS
|
|
209
|
+
for fence in &config_file.fences[fence_index].fences {
|
|
210
|
+
fences.push(parse_fence_info(
|
|
211
|
+
fence,
|
|
212
|
+
&mut total_prob,
|
|
213
|
+
bet_amount,
|
|
214
|
+
&&config_file.dresses[dress_index].dresses,
|
|
215
|
+
));
|
|
216
|
+
}
|
|
217
|
+
// NOW NEED TO APPLY THE HR TO "x" and generate Pigs for Fence
|
|
218
|
+
let mut pig_pens: Vec<Vec<Pig>> = Vec::with_capacity(fences.len());
|
|
219
|
+
for mut fence in &mut fences {
|
|
220
|
+
if fence.hr == -1.0 {
|
|
221
|
+
fence.hr = 1.0 / (1.0 - total_prob);
|
|
222
|
+
fence.avg_win = fence.hr * fence.rtp;
|
|
223
|
+
}
|
|
224
|
+
println!("\nCreating {} Fence\n", fence.name);
|
|
225
|
+
sort_wins_by_parameter(&mut fence, &force_options, &mut lookup_table);
|
|
226
|
+
|
|
227
|
+
if !fence.win_type {
|
|
228
|
+
let mut win_range_params: Vec<&mut Dress> = Vec::new();
|
|
229
|
+
for dress in &mut fence.dresses {
|
|
230
|
+
win_range_params.push(dress);
|
|
231
|
+
}
|
|
232
|
+
let mut win_array: Vec<f64> = Vec::with_capacity(fence.win_dist.keys().len());
|
|
233
|
+
for win in fence.win_dist.keys() {
|
|
234
|
+
win_array.push(win.0);
|
|
235
|
+
|
|
236
|
+
if !sorted_wins.contains(&win.0) {
|
|
237
|
+
sorted_wins.push(win.0);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
win_array.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
|
241
|
+
let max_win = win_array[win_array.len() - 1];
|
|
242
|
+
let min_win = win_array[0];
|
|
243
|
+
|
|
244
|
+
let pig_heaven = PigHeaven {
|
|
245
|
+
bet_amount: bet_amount,
|
|
246
|
+
wins: win_array,
|
|
247
|
+
rtp: fence.rtp,
|
|
248
|
+
pig_params: win_range_params,
|
|
249
|
+
num_pigs: num_pigs_per_fence / threads_for_fence_construction,
|
|
250
|
+
max_win: max_win,
|
|
251
|
+
min_win: min_win,
|
|
252
|
+
avg_win: fence.avg_win * bet_amount,
|
|
253
|
+
};
|
|
254
|
+
let thread_pool = rayon::ThreadPoolBuilder::new()
|
|
255
|
+
.num_threads(threads_for_fence_construction as usize)
|
|
256
|
+
.build()
|
|
257
|
+
.unwrap();
|
|
258
|
+
|
|
259
|
+
// Use Rayon's par_bridge method to process x1 and x2 concurrently
|
|
260
|
+
let fence_pigs: Vec<Pig> = thread_pool.install(|| {
|
|
261
|
+
(0..threads_for_fence_construction)
|
|
262
|
+
.into_par_iter()
|
|
263
|
+
.flat_map(|_| {
|
|
264
|
+
create_ancestors(
|
|
265
|
+
&pig_heaven,
|
|
266
|
+
fence.min_mean_to_median,
|
|
267
|
+
fence.max_mean_to_median,
|
|
268
|
+
)
|
|
269
|
+
})
|
|
270
|
+
.collect()
|
|
271
|
+
});
|
|
272
|
+
pig_pens.push(fence_pigs);
|
|
273
|
+
} else {
|
|
274
|
+
sorted_wins.push(fence.avg_win)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
sorted_wins.sort_by(|a, b| a.partial_cmp(&b).unwrap());
|
|
279
|
+
|
|
280
|
+
let sorted_wins_array = Array1::from_vec(sorted_wins);
|
|
281
|
+
|
|
282
|
+
let thread_pool_show_pigs = rayon::ThreadPoolBuilder::new()
|
|
283
|
+
.num_threads(threads_for_show_construction as usize)
|
|
284
|
+
.build()
|
|
285
|
+
.unwrap();
|
|
286
|
+
let mut show_pigs: Vec<ShowPig> = thread_pool_show_pigs.install(|| {
|
|
287
|
+
(0..threads_for_show_construction)
|
|
288
|
+
.into_par_iter()
|
|
289
|
+
.flat_map(|_| {
|
|
290
|
+
create_show_pigs(
|
|
291
|
+
&fences,
|
|
292
|
+
num_show_pigs / threads_for_show_construction,
|
|
293
|
+
test_spins,
|
|
294
|
+
test_spins_weights,
|
|
295
|
+
simulation_trials,
|
|
296
|
+
bet_amount,
|
|
297
|
+
1,
|
|
298
|
+
&sorted_wins_array,
|
|
299
|
+
&pig_pens,
|
|
300
|
+
rtp,
|
|
301
|
+
min_mean_to_median,
|
|
302
|
+
max_mean_to_median,
|
|
303
|
+
pmb_rtp,
|
|
304
|
+
)
|
|
305
|
+
})
|
|
306
|
+
.collect()
|
|
307
|
+
});
|
|
308
|
+
show_pigs.sort_by(|a, b| b.success_score.partial_cmp(&a.success_score).unwrap());
|
|
309
|
+
|
|
310
|
+
print_information(
|
|
311
|
+
&show_pigs,
|
|
312
|
+
&fences,
|
|
313
|
+
simulation_trials,
|
|
314
|
+
&sorted_wins_array,
|
|
315
|
+
bet_amount,
|
|
316
|
+
test_spins,
|
|
317
|
+
&pig_pens,
|
|
318
|
+
game_name.to_string(),
|
|
319
|
+
bet_type.to_string(),
|
|
320
|
+
user_game_build_path.to_string(),
|
|
321
|
+
pmb_rtp,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
fn print_information(
|
|
326
|
+
show_pigs: &Vec<ShowPig>,
|
|
327
|
+
fences: &Vec<Fence>,
|
|
328
|
+
trials: u32,
|
|
329
|
+
sorted_wins: &Array1<f64>,
|
|
330
|
+
bet_amount: f64,
|
|
331
|
+
test_spins: &Vec<u32>,
|
|
332
|
+
pig_pens: &Vec<Vec<Pig>>,
|
|
333
|
+
game_name: String,
|
|
334
|
+
bet_type: String,
|
|
335
|
+
user_game_build_path: String,
|
|
336
|
+
pmb_rtp: f64,
|
|
337
|
+
) {
|
|
338
|
+
let mut win_dist_index_map: HashMap<F64Wrapper, usize> =
|
|
339
|
+
HashMap::with_capacity(sorted_wins.len());
|
|
340
|
+
let mut count: usize = 0;
|
|
341
|
+
|
|
342
|
+
for win in sorted_wins {
|
|
343
|
+
win_dist_index_map.insert(F64Wrapper(win.clone()), count);
|
|
344
|
+
count += 1;
|
|
345
|
+
}
|
|
346
|
+
let num_pigs = 10;
|
|
347
|
+
|
|
348
|
+
(0..num_pigs).into_par_iter().for_each(|pig_index| {
|
|
349
|
+
println!("Printing info for Distribution {}", pig_index + 1);
|
|
350
|
+
let mut lookup_table: HashMap<u32, LookUpTableEntry> =
|
|
351
|
+
match read_look_up_table(&game_name, &bet_type, user_game_build_path.to_string()) {
|
|
352
|
+
Ok(table) => table,
|
|
353
|
+
Err(err) => {
|
|
354
|
+
eprintln!("Error reading the CSV file: {}", err);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
let (succuss_vals, weights, wins_for_fences, weights_from_pigs) = recreate_show_pig(
|
|
359
|
+
&show_pigs[pig_index],
|
|
360
|
+
fences,
|
|
361
|
+
trials,
|
|
362
|
+
sorted_wins,
|
|
363
|
+
bet_amount,
|
|
364
|
+
test_spins,
|
|
365
|
+
pig_pens,
|
|
366
|
+
&win_dist_index_map,
|
|
367
|
+
pmb_rtp,
|
|
368
|
+
);
|
|
369
|
+
{
|
|
370
|
+
let file_path = Path::new(&user_game_build_path)
|
|
371
|
+
.join("optimization_files")
|
|
372
|
+
.join(format!("{}_0_{}.csv", bet_type, pig_index + 1));
|
|
373
|
+
// let mut file = File::create(file_path).expect("Failed to create file");
|
|
374
|
+
let mut file = BufWriter::new(File::create(file_path).unwrap());
|
|
375
|
+
for (_index, value) in succuss_vals.iter().enumerate() {
|
|
376
|
+
if _index == succuss_vals.len() - 1 {
|
|
377
|
+
write!(file, "{}", value).expect("Failed to write to file");
|
|
378
|
+
} else {
|
|
379
|
+
write!(file, "{}, ", value).expect("Failed to write to file");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
let mut non_win_fence_count: usize = 0;
|
|
384
|
+
for fence in fences {
|
|
385
|
+
if fence.win_type {
|
|
386
|
+
for (index, book_id_list) in &fence.win_dist {
|
|
387
|
+
for book_id in book_id_list {
|
|
388
|
+
lookup_table.entry(*book_id).and_modify(|value| {
|
|
389
|
+
value.weight = ((1.0 / fence.hr / (book_id_list.len() as f64))
|
|
390
|
+
* (2_f64.powf(50.0) as f64))
|
|
391
|
+
as u64;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
for (win, book_id_list) in &fence.win_dist {
|
|
397
|
+
let mut weight = 0.0;
|
|
398
|
+
for win_index in 0..wins_for_fences[non_win_fence_count].len() {
|
|
399
|
+
if (wins_for_fences[non_win_fence_count][win_index] - win.0).powi(2)
|
|
400
|
+
< 0.000000000000000000001
|
|
401
|
+
{
|
|
402
|
+
weight = weights_from_pigs[non_win_fence_count][win_index]
|
|
403
|
+
/ fence.hr
|
|
404
|
+
/ (book_id_list.len() as f64);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for book_id in book_id_list {
|
|
409
|
+
lookup_table.entry(*book_id).and_modify(|value| {
|
|
410
|
+
value.weight = (weight * (2_f64.powf(50.0) as f64)) as u64;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
non_win_fence_count += 1;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
let mut rtp = 0.0;
|
|
418
|
+
let mut sum_dist = 0.0;
|
|
419
|
+
let mut sorted_indexes: Vec<&u32> = lookup_table.keys().into_iter().collect();
|
|
420
|
+
sorted_indexes.sort();
|
|
421
|
+
|
|
422
|
+
for index in &sorted_indexes {
|
|
423
|
+
let entry = lookup_table.get(index).unwrap();
|
|
424
|
+
rtp += entry.weight as f64 * entry.win;
|
|
425
|
+
sum_dist += entry.weight as f64;
|
|
426
|
+
}
|
|
427
|
+
rtp = rtp / sum_dist;
|
|
428
|
+
if pig_index == 0 {
|
|
429
|
+
{
|
|
430
|
+
let file_path = Path::new(&user_game_build_path)
|
|
431
|
+
.join("publish_files")
|
|
432
|
+
.join(format!("lookUpTable_{}_0.csv", bet_type));
|
|
433
|
+
|
|
434
|
+
// let mut file = File::create(file_path).expect("Failed to create file");
|
|
435
|
+
let mut file = BufWriter::new(File::create(file_path).unwrap());
|
|
436
|
+
for index in &sorted_indexes {
|
|
437
|
+
let entry = lookup_table.get(index).unwrap();
|
|
438
|
+
let rounded_win = (entry.win * 100.0).round(); //format!("{:.2}", entry.win);
|
|
439
|
+
write!(file, "{},{},{}\n", entry.id, entry.weight, rounded_win as u64)
|
|
440
|
+
.expect("Failed to write to file");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
{
|
|
445
|
+
let file_path = Path::new(&user_game_build_path)
|
|
446
|
+
.join("optimization_files")
|
|
447
|
+
.join(format!("{}_0_{}.csv", bet_type, pig_index + 1));
|
|
448
|
+
// let mut file = File::create(file_path).expect("Failed to create file");
|
|
449
|
+
let mut file = BufWriter::new(File::create(file_path).unwrap());
|
|
450
|
+
write!(file, "Name,Pig{}\n", (pig_index + 1)).expect("Failed to write to file");
|
|
451
|
+
write!(file, "Score,{}\n", show_pigs[pig_index].success_score)
|
|
452
|
+
.expect("Failed to write to file");
|
|
453
|
+
write!(file, "LockedUpRTP,\n").expect("Failed to write to file");
|
|
454
|
+
write!(file, "Rtp,{}\n", rtp).expect("Failed to write to file");
|
|
455
|
+
write!(file, "Win Ranges\n").expect("error");
|
|
456
|
+
get_win_ranges(&mut file, &weights, sorted_wins);
|
|
457
|
+
write!(file, "Distribution\n").expect("Failed to write to file");
|
|
458
|
+
for index in &sorted_indexes {
|
|
459
|
+
let entry = lookup_table.get(index).unwrap();
|
|
460
|
+
let rounded_win = format!("{:.2}", entry.win);
|
|
461
|
+
write!(file, "{},{},{}\n", entry.id, entry.weight, rounded_win)
|
|
462
|
+
.expect("Failed to write to file");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
fn get_win_ranges<T: std::io::Write>(
|
|
470
|
+
file: &mut T,
|
|
471
|
+
weights: &Array1<f64>,
|
|
472
|
+
sorted_wins: &Array1<f64>,
|
|
473
|
+
) {
|
|
474
|
+
let mut win_ranges: Vec<((f64, f64), f64)> = vec![
|
|
475
|
+
((0.0, 0.1), 0.0),
|
|
476
|
+
((0.1, 1.0), 0.0),
|
|
477
|
+
((1.0, 2.0), 0.0),
|
|
478
|
+
((2.0, 3.0), 0.0),
|
|
479
|
+
((3.0, 5.0), 0.0),
|
|
480
|
+
((5.0, 10.0), 0.0),
|
|
481
|
+
((10.0, 20.0), 0.0),
|
|
482
|
+
((20.0, 50.0), 0.0),
|
|
483
|
+
((50.0, 100.0), 0.0),
|
|
484
|
+
((100.0, 200.0), 0.0),
|
|
485
|
+
((200.0, 500.0), 0.0),
|
|
486
|
+
((500.0, 1000.0), 0.0),
|
|
487
|
+
((1000.0, 2000.0), 0.0),
|
|
488
|
+
((2000.0, 3000.0), 0.0),
|
|
489
|
+
((3000.0, 5000.0), 0.0),
|
|
490
|
+
((5000.0, 7500.0), 0.0),
|
|
491
|
+
((7500.0, 10000.0), 0.0),
|
|
492
|
+
((10000.0, 15000.0), 0.0),
|
|
493
|
+
((15000.0, 20000.0), 0.0),
|
|
494
|
+
((20000.0, 20001.0), 0.0),
|
|
495
|
+
];
|
|
496
|
+
for win_index in 0..sorted_wins.len() {
|
|
497
|
+
for range_index in 0..win_ranges.len() {
|
|
498
|
+
let ((low, high), tot) = win_ranges[range_index];
|
|
499
|
+
if sorted_wins[win_index] >= low && sorted_wins[win_index] < high {
|
|
500
|
+
win_ranges[range_index] = ((low, high), tot + weights[win_index]);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
for ((low, high), tot) in win_ranges {
|
|
505
|
+
if tot == 0.0 {
|
|
506
|
+
write!(file, "{},{},1 in never\n", low, high).expect("failed");
|
|
507
|
+
} else {
|
|
508
|
+
let prob = 1.0 / tot;
|
|
509
|
+
write!(file, "{},{},1 in {:.3}\n", low, high, prob).expect("failed");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
fn recreate_show_pig(
|
|
515
|
+
show_pig: &ShowPig,
|
|
516
|
+
fences: &Vec<Fence>,
|
|
517
|
+
trials: u32,
|
|
518
|
+
sorted_wins: &Array1<f64>,
|
|
519
|
+
bet_amount: f64,
|
|
520
|
+
test_spins: &Vec<u32>,
|
|
521
|
+
pig_pens: &Vec<Vec<Pig>>,
|
|
522
|
+
win_dist_index_map: &HashMap<F64Wrapper, usize>,
|
|
523
|
+
pmb_rtp: f64,
|
|
524
|
+
) -> (Vec<f64>, Array1<f64>, Vec<Vec<f64>>, Vec<Vec<f64>>) {
|
|
525
|
+
let mut weights: Array1<f64> = Array1::from_vec(vec![0.0; sorted_wins.len()]);
|
|
526
|
+
let mut wins_for_fences: Vec<Vec<f64>> = Vec::new();
|
|
527
|
+
let mut weights_from_pigs: Vec<Vec<f64>> = Vec::new();
|
|
528
|
+
let mut random_weights_to_apply: Vec<Vec<Vec<f64>>> = Vec::new();
|
|
529
|
+
for fence in fences {
|
|
530
|
+
if !fence.win_type {
|
|
531
|
+
let mut _win_vec: Vec<f64> = Vec::with_capacity(fence.win_dist.len());
|
|
532
|
+
for (key, _win) in &fence.win_dist {
|
|
533
|
+
_win_vec.push(key.0);
|
|
534
|
+
}
|
|
535
|
+
_win_vec.sort_by(|a, b| a.partial_cmp(&b).unwrap());
|
|
536
|
+
wins_for_fences.push(_win_vec);
|
|
537
|
+
weights_from_pigs.push(vec![0.0; fence.win_dist.len()]);
|
|
538
|
+
random_weights_to_apply.push(Vec::new());
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
let mut non_win_type_count: usize = 0;
|
|
542
|
+
for fence in fences {
|
|
543
|
+
if fence.win_type {
|
|
544
|
+
if let Some(index) = win_dist_index_map.get(&F64Wrapper(fence.avg_win)) {
|
|
545
|
+
weights[*index] += 1.0 / fence.hr;
|
|
546
|
+
// norm_factor += 1.0/fence.hr;
|
|
547
|
+
}
|
|
548
|
+
} else {
|
|
549
|
+
random_weights_to_apply[non_win_type_count].push(vec![
|
|
550
|
+
0.0;
|
|
551
|
+
wins_for_fences
|
|
552
|
+
[non_win_type_count]
|
|
553
|
+
.len()
|
|
554
|
+
]);
|
|
555
|
+
random_weights_to_apply[non_win_type_count].push(vec![
|
|
556
|
+
0.0;
|
|
557
|
+
wins_for_fences
|
|
558
|
+
[non_win_type_count]
|
|
559
|
+
.len()
|
|
560
|
+
]);
|
|
561
|
+
|
|
562
|
+
let pig_index = show_pig.pig_indexes[non_win_type_count];
|
|
563
|
+
let random_pig = &pig_pens[non_win_type_count][pig_index];
|
|
564
|
+
get_weights(
|
|
565
|
+
&wins_for_fences[non_win_type_count],
|
|
566
|
+
&mut weights_from_pigs[non_win_type_count],
|
|
567
|
+
&random_pig.amps,
|
|
568
|
+
&random_pig.mus,
|
|
569
|
+
&random_pig.stds,
|
|
570
|
+
&random_pig.params,
|
|
571
|
+
&random_pig.apply_parms,
|
|
572
|
+
&random_pig.random_seeds,
|
|
573
|
+
&random_pig.random_weights,
|
|
574
|
+
&random_pig.random_apply_params,
|
|
575
|
+
&mut random_weights_to_apply[non_win_type_count],
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
let mut n = 0;
|
|
579
|
+
while n < weights_from_pigs[non_win_type_count].len() {
|
|
580
|
+
if let Some(index) =
|
|
581
|
+
win_dist_index_map.get(&F64Wrapper(wins_for_fences[non_win_type_count][n]))
|
|
582
|
+
{
|
|
583
|
+
weights[*index] += weights_from_pigs[non_win_type_count][n] / fence.hr;
|
|
584
|
+
// norm_factor += weights_from_pigs[non_win_type_count][n]/fence.hr;
|
|
585
|
+
}
|
|
586
|
+
n += 1;
|
|
587
|
+
}
|
|
588
|
+
non_win_type_count += 1;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
let mut rtp = 0.0;
|
|
592
|
+
for index in 0..sorted_wins.len() {
|
|
593
|
+
rtp += sorted_wins[index] * weights[index]
|
|
594
|
+
}
|
|
595
|
+
let success = run_enhanced_simulation(
|
|
596
|
+
&sorted_wins,
|
|
597
|
+
&weights,
|
|
598
|
+
test_spins[test_spins.len() - 1usize] as usize,
|
|
599
|
+
trials as usize,
|
|
600
|
+
bet_amount,
|
|
601
|
+
test_spins,
|
|
602
|
+
pmb_rtp,
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
return (success, weights, wins_for_fences, weights_from_pigs);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
fn create_show_pigs(
|
|
609
|
+
fences: &Vec<Fence>,
|
|
610
|
+
num_pigs: u32,
|
|
611
|
+
test_spins: &Vec<u32>,
|
|
612
|
+
test_spins_weights: &Vec<f64>,
|
|
613
|
+
trials: u32,
|
|
614
|
+
bet_amount: f64,
|
|
615
|
+
process_id: u32,
|
|
616
|
+
sorted_wins: &Array1<f64>,
|
|
617
|
+
pig_pens: &Vec<Vec<Pig>>,
|
|
618
|
+
rtp: f64,
|
|
619
|
+
min_mean_to_median: f64,
|
|
620
|
+
max_mean_to_median: f64,
|
|
621
|
+
pmb_rtp: f64,
|
|
622
|
+
) -> Vec<ShowPig> {
|
|
623
|
+
println!("Creating Initial Distributions");
|
|
624
|
+
let mut best_score = 0.0;
|
|
625
|
+
let mut rng = rand::thread_rng();
|
|
626
|
+
let mut show_pigs: Vec<ShowPig> = Vec::with_capacity(num_pigs as usize);
|
|
627
|
+
// First Construct the has map
|
|
628
|
+
let mut win_dist_index_map: HashMap<F64Wrapper, usize> =
|
|
629
|
+
HashMap::with_capacity(sorted_wins.len());
|
|
630
|
+
let mut count: usize = 0;
|
|
631
|
+
for win in sorted_wins {
|
|
632
|
+
win_dist_index_map.insert(F64Wrapper(win.clone()), count);
|
|
633
|
+
count += 1;
|
|
634
|
+
}
|
|
635
|
+
let mut weights: Array1<f64> = Array1::from_vec(vec![0.0; sorted_wins.len()]);
|
|
636
|
+
// need to construct wins for each fence
|
|
637
|
+
let mut wins_for_fences: Vec<Vec<f64>> = Vec::new();
|
|
638
|
+
let mut weights_from_pigs: Vec<Vec<f64>> = Vec::new();
|
|
639
|
+
let mut random_weights_to_apply: Vec<Vec<Vec<f64>>> = Vec::new();
|
|
640
|
+
let mut banks: Array1<f64> =
|
|
641
|
+
Array1::zeros((test_spins[test_spins.len() - 1] * trials) as usize);
|
|
642
|
+
for fence in fences {
|
|
643
|
+
if !fence.win_type {
|
|
644
|
+
let mut _win_vec: Vec<f64> = Vec::with_capacity(fence.win_dist.len());
|
|
645
|
+
for (key, _win) in &fence.win_dist {
|
|
646
|
+
_win_vec.push(key.0);
|
|
647
|
+
}
|
|
648
|
+
_win_vec.sort_by(|a, b| a.partial_cmp(&b).unwrap());
|
|
649
|
+
wins_for_fences.push(_win_vec);
|
|
650
|
+
weights_from_pigs.push(vec![0.0; fence.win_dist.len()]);
|
|
651
|
+
random_weights_to_apply.push(Vec::new());
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
for p in 0..num_pigs {
|
|
656
|
+
let mut score = 0.0;
|
|
657
|
+
let mut pig_indexes: Vec<usize> = Vec::new();
|
|
658
|
+
for w_index in 0..weights.len() {
|
|
659
|
+
weights[w_index] = 0.0;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
pig_indexes = Vec::new();
|
|
663
|
+
if (p + 1) % (num_pigs / 2) == 0 {
|
|
664
|
+
println!(
|
|
665
|
+
"Thread {}: {}% done",
|
|
666
|
+
process_id,
|
|
667
|
+
100f32 * ((p + 1) as f32) / (num_pigs as f32)
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
let mut non_win_type_count: usize = 0;
|
|
671
|
+
for fence in fences {
|
|
672
|
+
if fence.win_type {
|
|
673
|
+
if let Some(index) = win_dist_index_map.get(&F64Wrapper(fence.avg_win)) {
|
|
674
|
+
weights[*index] += 1.0 / fence.hr;
|
|
675
|
+
// norm_factor += 1.0/fence.hr;
|
|
676
|
+
}
|
|
677
|
+
} else {
|
|
678
|
+
if p == 0 {
|
|
679
|
+
random_weights_to_apply[non_win_type_count].push(vec![
|
|
680
|
+
0.0;
|
|
681
|
+
wins_for_fences
|
|
682
|
+
[non_win_type_count]
|
|
683
|
+
.len()
|
|
684
|
+
]);
|
|
685
|
+
random_weights_to_apply[non_win_type_count].push(vec![
|
|
686
|
+
0.0;
|
|
687
|
+
wins_for_fences
|
|
688
|
+
[non_win_type_count]
|
|
689
|
+
.len()
|
|
690
|
+
]);
|
|
691
|
+
}
|
|
692
|
+
let pig_index = rng.gen_range(0..=pig_pens[non_win_type_count].len() - 1) as usize;
|
|
693
|
+
pig_indexes.push(pig_index);
|
|
694
|
+
let random_pig = &pig_pens[non_win_type_count][pig_index];
|
|
695
|
+
get_weights(
|
|
696
|
+
&wins_for_fences[non_win_type_count],
|
|
697
|
+
&mut weights_from_pigs[non_win_type_count],
|
|
698
|
+
&random_pig.amps,
|
|
699
|
+
&random_pig.mus,
|
|
700
|
+
&random_pig.stds,
|
|
701
|
+
&random_pig.params,
|
|
702
|
+
&random_pig.apply_parms,
|
|
703
|
+
&random_pig.random_seeds,
|
|
704
|
+
&random_pig.random_weights,
|
|
705
|
+
&random_pig.random_apply_params,
|
|
706
|
+
&mut random_weights_to_apply[non_win_type_count],
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
let mut n = 0;
|
|
710
|
+
while n < weights_from_pigs[non_win_type_count].len() {
|
|
711
|
+
if let Some(index) =
|
|
712
|
+
win_dist_index_map.get(&F64Wrapper(wins_for_fences[non_win_type_count][n]))
|
|
713
|
+
{
|
|
714
|
+
weights[*index] += weights_from_pigs[non_win_type_count][n] / fence.hr;
|
|
715
|
+
// norm_factor += weights_from_ pigs[non_win_type_count][n]/fence.hr;
|
|
716
|
+
}
|
|
717
|
+
n += 1;
|
|
718
|
+
}
|
|
719
|
+
non_win_type_count += 1;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
score = run_simulation(
|
|
724
|
+
&sorted_wins,
|
|
725
|
+
&weights,
|
|
726
|
+
test_spins[test_spins.len() - 1],
|
|
727
|
+
trials,
|
|
728
|
+
bet_amount,
|
|
729
|
+
test_spins,
|
|
730
|
+
&mut banks,
|
|
731
|
+
test_spins_weights,
|
|
732
|
+
pmb_rtp,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// RESET THE VALUES
|
|
736
|
+
if score != 0.0 && score >= best_score {
|
|
737
|
+
best_score = score;
|
|
738
|
+
show_pigs.push(ShowPig {
|
|
739
|
+
pig_indexes: pig_indexes,
|
|
740
|
+
success_score: score,
|
|
741
|
+
})
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
show_pigs
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
fn sort_wins_by_parameter(
|
|
748
|
+
fence: &mut Fence,
|
|
749
|
+
force_options: &Vec<SearchResult>,
|
|
750
|
+
lookup_table: &mut HashMap<u32, LookUpTableEntry>,
|
|
751
|
+
) {
|
|
752
|
+
if fence.win_type {
|
|
753
|
+
let identity_win = fence.identity_condition.win_range_start;
|
|
754
|
+
let keys_to_remove: Vec<u32> = if fence.identity_condition.opposite {
|
|
755
|
+
lookup_table
|
|
756
|
+
.iter()
|
|
757
|
+
.filter(|(_, result)| result.win != identity_win)
|
|
758
|
+
.map(|(&k, _)| k)
|
|
759
|
+
.collect()
|
|
760
|
+
} else {
|
|
761
|
+
lookup_table
|
|
762
|
+
.iter()
|
|
763
|
+
.filter(|(_, result)| result.win == identity_win)
|
|
764
|
+
.map(|(&k, _)| k)
|
|
765
|
+
.collect()
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
for key in keys_to_remove {
|
|
769
|
+
let entry = lookup_table.remove(&key);
|
|
770
|
+
if let Some(result) = entry {
|
|
771
|
+
fence
|
|
772
|
+
.win_dist
|
|
773
|
+
.entry(F64Wrapper(result.win))
|
|
774
|
+
.or_insert(Vec::new())
|
|
775
|
+
.push(result.id);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
let i_c = &mut fence.identity_condition;
|
|
780
|
+
|
|
781
|
+
if i_c.search.is_empty() && i_c.win_range_start == -1.0 && i_c.opposite == false {
|
|
782
|
+
for (book_id, entry) in lookup_table {
|
|
783
|
+
fence
|
|
784
|
+
.win_dist
|
|
785
|
+
.entry(F64Wrapper(entry.win))
|
|
786
|
+
.or_insert(Vec::new())
|
|
787
|
+
.push(entry.id);
|
|
788
|
+
// lookup_table.remove(book_id);
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
for option in force_options {
|
|
792
|
+
let mut condition_satisfied = true; // Start assuming the condition is satisfied
|
|
793
|
+
for (_i, i_c_key) in i_c.search.iter().enumerate() {
|
|
794
|
+
if i_c_key.value != "None" {
|
|
795
|
+
// Retrieve the value from the HashMap based on the i_c_key which is the key
|
|
796
|
+
// Compare the retrieved value after converting it to a string
|
|
797
|
+
let search_key_exists = option.search.iter().any(|search_key| {
|
|
798
|
+
search_key.name == i_c_key.name && search_key.value == i_c_key.value
|
|
799
|
+
});
|
|
800
|
+
if !search_key_exists {
|
|
801
|
+
// If the key is not found in the HashMap, the condition is not satisfied
|
|
802
|
+
condition_satisfied = false;
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if i_c.opposite {
|
|
809
|
+
condition_satisfied = !condition_satisfied;
|
|
810
|
+
}
|
|
811
|
+
if condition_satisfied {
|
|
812
|
+
for book_id in &option.bookIds {
|
|
813
|
+
if lookup_table.contains_key(book_id) {
|
|
814
|
+
let entry = lookup_table.get(book_id).unwrap();
|
|
815
|
+
fence
|
|
816
|
+
.win_dist
|
|
817
|
+
.entry(F64Wrapper(entry.win))
|
|
818
|
+
.or_insert(Vec::new())
|
|
819
|
+
.push(entry.id);
|
|
820
|
+
lookup_table.remove(book_id);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
fn parse_fence_info(
|
|
830
|
+
fence: &FenceJson,
|
|
831
|
+
total_prob: &mut f64,
|
|
832
|
+
bet_amount: f64,
|
|
833
|
+
json_dresses: &Vec<DressJson>,
|
|
834
|
+
) -> Fence {
|
|
835
|
+
let name: String = fence.name.clone();
|
|
836
|
+
// Need to get the Values from fence and convert them to floats.
|
|
837
|
+
let mut avg_win = fence
|
|
838
|
+
.avg_win
|
|
839
|
+
.clone()
|
|
840
|
+
.unwrap_or(String::from("-1"))
|
|
841
|
+
.parse::<f64>()
|
|
842
|
+
.unwrap();
|
|
843
|
+
let hr_str = fence.hr.clone().unwrap_or(String::from("-1"));
|
|
844
|
+
let mut hr: f64 = -1.0;
|
|
845
|
+
|
|
846
|
+
if hr_str != String::from("x") {
|
|
847
|
+
hr = fence
|
|
848
|
+
.hr
|
|
849
|
+
.clone()
|
|
850
|
+
.unwrap_or(String::from("-1"))
|
|
851
|
+
.parse::<f64>()
|
|
852
|
+
.unwrap();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
let mut rtp = fence
|
|
856
|
+
.rtp
|
|
857
|
+
.clone()
|
|
858
|
+
.unwrap_or(String::from("-1"))
|
|
859
|
+
.parse::<f64>()
|
|
860
|
+
.unwrap();
|
|
861
|
+
let identity_condition = fence.identity_condition.clone();
|
|
862
|
+
|
|
863
|
+
if hr_str != "x" && (hr > 0.0) & (rtp > 0.0) {
|
|
864
|
+
avg_win = hr * rtp;
|
|
865
|
+
}
|
|
866
|
+
if hr_str != "x" && (hr > 0.0) & (avg_win > 0.0) {
|
|
867
|
+
rtp = avg_win / hr;
|
|
868
|
+
}
|
|
869
|
+
if hr_str != "x" && hr < 0.0 && (rtp > 0.0) & (avg_win > 0.0) {
|
|
870
|
+
hr = avg_win / rtp / bet_amount
|
|
871
|
+
}
|
|
872
|
+
if hr > 0.0 {
|
|
873
|
+
*total_prob += 1.0 / hr;
|
|
874
|
+
}
|
|
875
|
+
// println!("name: {},avg_win: {},hr: {},rtp: {},ic:{}",name,avg_win,hr,rtp,identity_condition) ;
|
|
876
|
+
|
|
877
|
+
let mut wins: Vec<f64> = vec![];
|
|
878
|
+
let mut win_type = false;
|
|
879
|
+
|
|
880
|
+
// println!("{}",split_string.len());
|
|
881
|
+
if identity_condition.win_range_start > -1.0
|
|
882
|
+
&& identity_condition.win_range_end == identity_condition.win_range_start
|
|
883
|
+
{
|
|
884
|
+
wins.push(identity_condition.win_range_end);
|
|
885
|
+
win_type = true;
|
|
886
|
+
}
|
|
887
|
+
let mut dresses: Vec<Dress> = Vec::new();
|
|
888
|
+
for json_dress in json_dresses {
|
|
889
|
+
let fence = json_dress.fence.clone();
|
|
890
|
+
if fence == name {
|
|
891
|
+
let scaler_factor = parse_scale_factor(&json_dress.scale_factor);
|
|
892
|
+
let identity_condition_win_range = json_dress
|
|
893
|
+
.identity_condition_win_range
|
|
894
|
+
.clone()
|
|
895
|
+
.unwrap_or([0.0, 0.0]);
|
|
896
|
+
let prob = json_dress.prob.clone().unwrap_or(1.0);
|
|
897
|
+
dresses.push(Dress {
|
|
898
|
+
fence: fence,
|
|
899
|
+
scale_factor: scaler_factor,
|
|
900
|
+
identity_condition_win_range: identity_condition_win_range,
|
|
901
|
+
prob: prob,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
let win_dist: HashMap<F64Wrapper, Vec<u32>> = HashMap::new();
|
|
907
|
+
let min_mean_to_median = fence
|
|
908
|
+
.min_mean_to_median
|
|
909
|
+
.clone()
|
|
910
|
+
.unwrap_or(String::from("0"))
|
|
911
|
+
.parse::<f64>()
|
|
912
|
+
.unwrap();
|
|
913
|
+
|
|
914
|
+
let max_mean_to_median = fence
|
|
915
|
+
.max_mean_to_median
|
|
916
|
+
.clone()
|
|
917
|
+
.unwrap_or(String::from("10"))
|
|
918
|
+
.parse::<f64>()
|
|
919
|
+
.unwrap();
|
|
920
|
+
|
|
921
|
+
return Fence {
|
|
922
|
+
name: name,
|
|
923
|
+
hr: hr,
|
|
924
|
+
rtp: rtp,
|
|
925
|
+
avg_win: avg_win,
|
|
926
|
+
identity_condition: identity_condition,
|
|
927
|
+
win_type: win_type,
|
|
928
|
+
dresses: dresses,
|
|
929
|
+
wins: wins,
|
|
930
|
+
win_dist: win_dist,
|
|
931
|
+
opposite_statement: false,
|
|
932
|
+
min_mean_to_median: min_mean_to_median,
|
|
933
|
+
max_mean_to_median: max_mean_to_median,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
pub struct Dress {
|
|
937
|
+
pub fence: String,
|
|
938
|
+
pub scale_factor: ScaleFactor,
|
|
939
|
+
pub identity_condition_win_range: [f64; 2],
|
|
940
|
+
pub prob: f64,
|
|
941
|
+
}
|
|
942
|
+
pub struct Fence {
|
|
943
|
+
pub name: String,
|
|
944
|
+
pub hr: f64,
|
|
945
|
+
pub rtp: f64,
|
|
946
|
+
pub avg_win: f64,
|
|
947
|
+
pub identity_condition: IdentityCondition,
|
|
948
|
+
pub win_type: bool,
|
|
949
|
+
pub dresses: Vec<Dress>,
|
|
950
|
+
pub wins: Vec<f64>,
|
|
951
|
+
pub win_dist: HashMap<F64Wrapper, Vec<u32>>,
|
|
952
|
+
pub opposite_statement: bool,
|
|
953
|
+
pub min_mean_to_median: f64,
|
|
954
|
+
pub max_mean_to_median: f64,
|
|
955
|
+
}
|
|
956
|
+
pub struct PigHeaven<'a> {
|
|
957
|
+
pub bet_amount: f64,
|
|
958
|
+
pub wins: Vec<f64>,
|
|
959
|
+
pub rtp: f64,
|
|
960
|
+
pub pig_params: Vec<&'a mut Dress>,
|
|
961
|
+
pub num_pigs: u32,
|
|
962
|
+
pub max_win: f64,
|
|
963
|
+
pub min_win: f64,
|
|
964
|
+
pub avg_win: f64,
|
|
965
|
+
}
|
|
966
|
+
#[derive(Clone)]
|
|
967
|
+
pub struct Pig {
|
|
968
|
+
pub amps: Vec<f64>,
|
|
969
|
+
pub mus: Vec<f64>,
|
|
970
|
+
pub stds: Vec<f64>,
|
|
971
|
+
pub params: Vec<f64>,
|
|
972
|
+
pub apply_parms: Vec<Vec<u16>>,
|
|
973
|
+
pub rtp: f64,
|
|
974
|
+
pub sum_dist: f64,
|
|
975
|
+
pub random_seeds: Vec<u32>,
|
|
976
|
+
pub random_weights: Vec<f64>,
|
|
977
|
+
pub random_apply_params: Vec<Vec<usize>>,
|
|
978
|
+
}
|
|
979
|
+
pub enum ScaleFactor {
|
|
980
|
+
Factor(f64),
|
|
981
|
+
FactorR(f64),
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
fn create_ancestors(
|
|
985
|
+
pig_heaven: &PigHeaven,
|
|
986
|
+
min_mean_to_median: f64,
|
|
987
|
+
max_mean_to_median: f64,
|
|
988
|
+
) -> Vec<Pig> {
|
|
989
|
+
let mut pos_pigs: Vec<Pig> = Vec::with_capacity((pig_heaven.num_pigs as f64).sqrt() as usize);
|
|
990
|
+
let mut neg_pigs: Vec<Pig> = Vec::with_capacity((pig_heaven.num_pigs as f64).sqrt() as usize);
|
|
991
|
+
let mut extra_params: Vec<Dress> = Vec::with_capacity(2);
|
|
992
|
+
println!("Creating Initial Random Distributions...");
|
|
993
|
+
let mut go_back_down = false;
|
|
994
|
+
let mut std_weight = 70.0;
|
|
995
|
+
|
|
996
|
+
let mut rng = rand::thread_rng();
|
|
997
|
+
|
|
998
|
+
let mut loop_count = 0;
|
|
999
|
+
let mut amps: Vec<f64> = Vec::with_capacity(15);
|
|
1000
|
+
let mut mus: Vec<f64> = Vec::with_capacity(15);
|
|
1001
|
+
let mut stds: Vec<f64> = Vec::with_capacity(15);
|
|
1002
|
+
let mut already_printed = false;
|
|
1003
|
+
let mut bool_added_extra_parms = false;
|
|
1004
|
+
|
|
1005
|
+
while (pos_pigs.len() as f64) < (pig_heaven.num_pigs as f64).sqrt()
|
|
1006
|
+
|| (neg_pigs.len() as f64) < (pig_heaven.num_pigs as f64).sqrt()
|
|
1007
|
+
{
|
|
1008
|
+
loop_count += 1;
|
|
1009
|
+
if !go_back_down {
|
|
1010
|
+
std_weight = f64::min(
|
|
1011
|
+
std_weight * (1.0 + 1.0 / (pig_heaven.num_pigs as f64).powf(0.9)),
|
|
1012
|
+
400.0,
|
|
1013
|
+
);
|
|
1014
|
+
} else {
|
|
1015
|
+
std_weight = f64::min(
|
|
1016
|
+
std_weight * (1.0 - 1.0 / (pig_heaven.num_pigs as f64).powf(0.9)),
|
|
1017
|
+
400.0,
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
if (std_weight - 400.0).abs() < 0.00001 {
|
|
1021
|
+
go_back_down = true;
|
|
1022
|
+
}
|
|
1023
|
+
std_weight = f64::max(std_weight, 20.0);
|
|
1024
|
+
if (std_weight - 20.0).abs() < 0.00001 {
|
|
1025
|
+
go_back_down = false;
|
|
1026
|
+
}
|
|
1027
|
+
let variables: u32 = rng.gen_range(5..=15);
|
|
1028
|
+
amps.clear();
|
|
1029
|
+
mus.clear();
|
|
1030
|
+
stds.clear();
|
|
1031
|
+
|
|
1032
|
+
if loop_count % 2 == 0 {
|
|
1033
|
+
for _ in 0..variables {
|
|
1034
|
+
amps.push(rng.gen_range(1..=14) as f64);
|
|
1035
|
+
let v = rng.gen::<f64>();
|
|
1036
|
+
let condition = v % 2.0 == 0.0;
|
|
1037
|
+
mus.push(
|
|
1038
|
+
pig_heaven.avg_win
|
|
1039
|
+
* ((v * 0.25 + 1.0) * condition as i32 as f64
|
|
1040
|
+
+ (1.0 - v * 0.25) * (!condition as i32 as f64))
|
|
1041
|
+
* (rng.gen_range(5..=10) as f64 / 10.0),
|
|
1042
|
+
);
|
|
1043
|
+
stds.push(rng.gen::<f64>() * 30.0 * rng.gen::<f64>() * std_weight);
|
|
1044
|
+
}
|
|
1045
|
+
} else {
|
|
1046
|
+
for _ in 0..variables {
|
|
1047
|
+
let v = rng.gen::<f64>();
|
|
1048
|
+
let random_value2: f64 = rng.gen();
|
|
1049
|
+
mus.push(f64::max(
|
|
1050
|
+
v * pig_heaven.avg_win + 0.01 * random_value2 * pig_heaven.max_win,
|
|
1051
|
+
pig_heaven.min_win,
|
|
1052
|
+
));
|
|
1053
|
+
stds.push(rng.gen::<f64>() * std_weight);
|
|
1054
|
+
amps.push(rng.gen::<f64>());
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
let mut params: Vec<f64> = Vec::new();
|
|
1059
|
+
let mut apply_parms: Vec<Vec<u16>> = Vec::new();
|
|
1060
|
+
let mut count = 0;
|
|
1061
|
+
|
|
1062
|
+
for dress in &pig_heaven.pig_params {
|
|
1063
|
+
if rng.gen::<f64>() < dress.prob {
|
|
1064
|
+
let scale_factor = match dress.scale_factor {
|
|
1065
|
+
ScaleFactor::Factor(factor) => factor,
|
|
1066
|
+
ScaleFactor::FactorR(factor) => factor * rng.gen::<f64>(),
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
let param_list = dress.identity_condition_win_range.clone();
|
|
1070
|
+
params.extend(param_list);
|
|
1071
|
+
params.push(scale_factor);
|
|
1072
|
+
|
|
1073
|
+
let indexes: Vec<u16> = (0..mus.len()).map(|i| i as u16).collect();
|
|
1074
|
+
apply_parms.insert(count, indexes);
|
|
1075
|
+
count += 1;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
for extra_dress in &extra_params {
|
|
1079
|
+
let scale_factor = match extra_dress.scale_factor {
|
|
1080
|
+
ScaleFactor::Factor(factor) => factor,
|
|
1081
|
+
ScaleFactor::FactorR(factor) => factor * rng.gen::<f64>(),
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
let param_list = extra_dress.identity_condition_win_range.clone();
|
|
1085
|
+
params.extend(param_list);
|
|
1086
|
+
params.push(scale_factor);
|
|
1087
|
+
|
|
1088
|
+
let indexes: Vec<u16> = (0..mus.len()).map(|i| i as u16).collect();
|
|
1089
|
+
apply_parms.insert(count, indexes);
|
|
1090
|
+
count += 1;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
let random_seed: Vec<u32> = vec![rng.gen_range(0..=1000000000) as u32];
|
|
1094
|
+
let random_weight: Vec<f64> = vec![rng.gen::<f64>()];
|
|
1095
|
+
let mut new_pig = Pig {
|
|
1096
|
+
amps: Vec::new(),
|
|
1097
|
+
mus: Vec::new(),
|
|
1098
|
+
stds: Vec::new(),
|
|
1099
|
+
params: Vec::new(),
|
|
1100
|
+
apply_parms: Vec::new(),
|
|
1101
|
+
// apply_parms: HashMap::new(),
|
|
1102
|
+
random_seeds: random_seed,
|
|
1103
|
+
random_weights: random_weight,
|
|
1104
|
+
random_apply_params: Vec::new(),
|
|
1105
|
+
rtp: 0.0,
|
|
1106
|
+
sum_dist: 0.0,
|
|
1107
|
+
};
|
|
1108
|
+
mem::swap(&mut new_pig.amps, &mut amps);
|
|
1109
|
+
mem::swap(&mut new_pig.mus, &mut mus);
|
|
1110
|
+
mem::swap(&mut new_pig.stds, &mut stds);
|
|
1111
|
+
mem::swap(&mut new_pig.params, &mut params);
|
|
1112
|
+
mem::swap(&mut new_pig.apply_parms, &mut apply_parms);
|
|
1113
|
+
|
|
1114
|
+
let (rtp, sum_dist) = get_weights_no_weight_array(
|
|
1115
|
+
&pig_heaven.wins,
|
|
1116
|
+
&new_pig.amps,
|
|
1117
|
+
&new_pig.mus,
|
|
1118
|
+
&new_pig.stds,
|
|
1119
|
+
&new_pig.params,
|
|
1120
|
+
&new_pig.apply_parms,
|
|
1121
|
+
&new_pig.random_seeds,
|
|
1122
|
+
&new_pig.random_weights,
|
|
1123
|
+
);
|
|
1124
|
+
new_pig.rtp += rtp;
|
|
1125
|
+
new_pig.sum_dist += sum_dist;
|
|
1126
|
+
if new_pig.rtp > pig_heaven.avg_win
|
|
1127
|
+
&& (pos_pigs.len() as f64) < (pig_heaven.num_pigs as f64).sqrt()
|
|
1128
|
+
{
|
|
1129
|
+
pos_pigs.push(new_pig);
|
|
1130
|
+
} else if new_pig.rtp < pig_heaven.avg_win
|
|
1131
|
+
&& (neg_pigs.len() as f64) < (pig_heaven.num_pigs as f64).sqrt()
|
|
1132
|
+
{
|
|
1133
|
+
neg_pigs.push(new_pig);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (pos_pigs.len() as f64) >= (pig_heaven.num_pigs as f64).sqrt() && !bool_added_extra_parms
|
|
1137
|
+
{
|
|
1138
|
+
bool_added_extra_parms = true;
|
|
1139
|
+
let dress = Dress {
|
|
1140
|
+
fence: "".to_string(),
|
|
1141
|
+
scale_factor: ScaleFactor::Factor(150.0), // Use the ScaleFactor enum variant Factor
|
|
1142
|
+
identity_condition_win_range: [pig_heaven.min_win, pig_heaven.avg_win / 2.0],
|
|
1143
|
+
prob: 1.0,
|
|
1144
|
+
};
|
|
1145
|
+
extra_params.push(dress);
|
|
1146
|
+
|
|
1147
|
+
let dress2 = Dress {
|
|
1148
|
+
fence: "".to_string(),
|
|
1149
|
+
scale_factor: ScaleFactor::Factor(0.0001), // Use the ScaleFactor enum variant Factor
|
|
1150
|
+
identity_condition_win_range: [pig_heaven.avg_win / 2.0, pig_heaven.max_win],
|
|
1151
|
+
prob: 1.0,
|
|
1152
|
+
};
|
|
1153
|
+
extra_params.push(dress2);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (neg_pigs.len() as f64) >= (pig_heaven.num_pigs as f64).sqrt() && !bool_added_extra_parms
|
|
1157
|
+
{
|
|
1158
|
+
bool_added_extra_parms = true;
|
|
1159
|
+
let dress = Dress {
|
|
1160
|
+
fence: "".to_string(),
|
|
1161
|
+
scale_factor: ScaleFactor::Factor(50.0),
|
|
1162
|
+
identity_condition_win_range: [pig_heaven.avg_win * 2.0, pig_heaven.max_win],
|
|
1163
|
+
prob: 1.0,
|
|
1164
|
+
};
|
|
1165
|
+
extra_params.push(dress);
|
|
1166
|
+
let dress2 = Dress {
|
|
1167
|
+
fence: "".to_string(),
|
|
1168
|
+
scale_factor: ScaleFactor::Factor(0.0001),
|
|
1169
|
+
identity_condition_win_range: [pig_heaven.min_win, pig_heaven.avg_win],
|
|
1170
|
+
prob: 1.0,
|
|
1171
|
+
};
|
|
1172
|
+
extra_params.push(dress2);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if loop_count > 5 * pig_heaven.num_pigs {
|
|
1176
|
+
if neg_pigs.len() > pos_pigs.len() {
|
|
1177
|
+
if !already_printed {
|
|
1178
|
+
println!("RTP too low...");
|
|
1179
|
+
already_printed = true;
|
|
1180
|
+
}
|
|
1181
|
+
} else {
|
|
1182
|
+
if !already_printed {
|
|
1183
|
+
println!("RTP too high...");
|
|
1184
|
+
already_printed = true;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
println!("Combining Criteria Distributions....");
|
|
1191
|
+
let mut pigs_for_fence: Vec<Pig> = Vec::new();
|
|
1192
|
+
let mut pig_count = 0;
|
|
1193
|
+
while pig_count < pos_pigs.len() * neg_pigs.len() {
|
|
1194
|
+
let mut pig_volatility_satisfied = false;
|
|
1195
|
+
let mut satisifed_count = 0;
|
|
1196
|
+
while !pig_volatility_satisfied {
|
|
1197
|
+
satisifed_count += 1;
|
|
1198
|
+
|
|
1199
|
+
let pos_pig_index = rng.gen_range(0..=pos_pigs.len() - 1);
|
|
1200
|
+
let neg_pig_index = rng.gen_range(0..=neg_pigs.len() - 1);
|
|
1201
|
+
|
|
1202
|
+
let pos_pig = &pos_pigs[pos_pig_index];
|
|
1203
|
+
let neg_pig = &neg_pigs[neg_pig_index];
|
|
1204
|
+
|
|
1205
|
+
let pig = breed_pigs(pos_pig, neg_pig, &pig_heaven);
|
|
1206
|
+
let mut weights: Vec<f64> = vec![0.0; pig_heaven.wins.len()];
|
|
1207
|
+
let mut random_weights_to_apply: Vec<Vec<f64>> = Vec::new();
|
|
1208
|
+
random_weights_to_apply.push(vec![0.0; pig_heaven.wins.len()]);
|
|
1209
|
+
random_weights_to_apply.push(vec![0.0; pig_heaven.wins.len()]);
|
|
1210
|
+
get_weights(
|
|
1211
|
+
&pig_heaven.wins,
|
|
1212
|
+
&mut weights,
|
|
1213
|
+
&pig.amps,
|
|
1214
|
+
&pig.mus,
|
|
1215
|
+
&pig.stds,
|
|
1216
|
+
&pig.params,
|
|
1217
|
+
&pig.apply_parms,
|
|
1218
|
+
&pig.random_seeds,
|
|
1219
|
+
&pig.random_weights,
|
|
1220
|
+
&pig.random_apply_params,
|
|
1221
|
+
&mut random_weights_to_apply,
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
let mut sum_dist: f64 = 0.0;
|
|
1225
|
+
let mut median = 0.0;
|
|
1226
|
+
for win_index in 0..pig_heaven.wins.len() {
|
|
1227
|
+
sum_dist += weights[win_index];
|
|
1228
|
+
if sum_dist >= 0.5 {
|
|
1229
|
+
median = pig_heaven.wins[win_index];
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if !(median > 0.0
|
|
1234
|
+
&& (pig_heaven.avg_win / median <= min_mean_to_median
|
|
1235
|
+
|| pig_heaven.avg_win / median >= max_mean_to_median))
|
|
1236
|
+
{
|
|
1237
|
+
pig_volatility_satisfied = true;
|
|
1238
|
+
}
|
|
1239
|
+
if satisifed_count % 500 == 0 {
|
|
1240
|
+
println!(
|
|
1241
|
+
"Mean to Median {} {} {}",
|
|
1242
|
+
pig_heaven.avg_win / median,
|
|
1243
|
+
min_mean_to_median,
|
|
1244
|
+
max_mean_to_median
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
if pig_volatility_satisfied {
|
|
1248
|
+
pigs_for_fence.push(breed_pigs(pos_pig, neg_pig, &pig_heaven));
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
pig_count += 1;
|
|
1252
|
+
}
|
|
1253
|
+
return pigs_for_fence;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
fn get_weights_no_weight_array(
|
|
1257
|
+
wins: &Vec<f64>,
|
|
1258
|
+
amps: &Vec<f64>,
|
|
1259
|
+
mus: &Vec<f64>,
|
|
1260
|
+
stds: &Vec<f64>,
|
|
1261
|
+
params: &Vec<f64>,
|
|
1262
|
+
apply_parms: &[Vec<u16>],
|
|
1263
|
+
// apply_parms: &HashMap<u16, Vec<u16>>,
|
|
1264
|
+
random_seeds: &Vec<u32>,
|
|
1265
|
+
random_weights: &Vec<f64>,
|
|
1266
|
+
) -> (f64, f64) {
|
|
1267
|
+
let rtp: f64;
|
|
1268
|
+
let mut sum_dist = 0.0;
|
|
1269
|
+
let mut total_win: f64 = 0.0;
|
|
1270
|
+
|
|
1271
|
+
let mut random_weights_to_apply: Vec<Vec<f64>> = Vec::with_capacity(random_seeds.len());
|
|
1272
|
+
|
|
1273
|
+
for index in 0..random_seeds.len() {
|
|
1274
|
+
random_weights_to_apply.push(vec![0.0; wins.len()]);
|
|
1275
|
+
let mut rng = StdRng::seed_from_u64(random_seeds[index] as u64);
|
|
1276
|
+
for array_index in 0..wins.len() {
|
|
1277
|
+
let random_num = rng.gen_range(-100..=100) as f64;
|
|
1278
|
+
random_weights_to_apply[index][array_index] =
|
|
1279
|
+
1.0 + ((random_num) / 100.0) * random_weights[index];
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
for index in 0..wins.len() {
|
|
1284
|
+
let mut total_weight = 0.0;
|
|
1285
|
+
for norm_index in 0..amps.len() {
|
|
1286
|
+
let mut weight = amps[norm_index]
|
|
1287
|
+
* (1.0
|
|
1288
|
+
+ 20000.0
|
|
1289
|
+
* (1.0 / ((stds[norm_index] * (2.0 * 3.14)).sqrt()))
|
|
1290
|
+
* ((2.71_f64).powf(
|
|
1291
|
+
-0.5 * ((wins[index] - mus[norm_index]) / stds[norm_index]).powf(2.0),
|
|
1292
|
+
)));
|
|
1293
|
+
|
|
1294
|
+
for param_index in 0..(params.len() / 3) {
|
|
1295
|
+
if let Some(apply_norm_indexes) = apply_parms.get(param_index) {
|
|
1296
|
+
if apply_norm_indexes
|
|
1297
|
+
.binary_search(&(norm_index as u16))
|
|
1298
|
+
.is_ok()
|
|
1299
|
+
&& wins[index] >= params[3 * param_index]
|
|
1300
|
+
&& wins[index] <= params[3 * param_index + 1]
|
|
1301
|
+
{
|
|
1302
|
+
weight *= params[3 * param_index + 2];
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
weight *= random_weights_to_apply[0][index];
|
|
1307
|
+
total_weight += weight
|
|
1308
|
+
}
|
|
1309
|
+
sum_dist += total_weight;
|
|
1310
|
+
total_win += total_weight * wins[index]
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
rtp = total_win / sum_dist;
|
|
1314
|
+
(rtp, sum_dist)
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
fn get_weights(
|
|
1318
|
+
wins: &Vec<f64>,
|
|
1319
|
+
weights: &mut Vec<f64>,
|
|
1320
|
+
amps: &Vec<f64>,
|
|
1321
|
+
mus: &Vec<f64>,
|
|
1322
|
+
stds: &Vec<f64>,
|
|
1323
|
+
params: &Vec<f64>,
|
|
1324
|
+
apply_parms: &Vec<Vec<u16>>,
|
|
1325
|
+
random_seeds: &Vec<u32>,
|
|
1326
|
+
random_weights: &Vec<f64>,
|
|
1327
|
+
random_apply_params: &Vec<Vec<usize>>,
|
|
1328
|
+
random_weights_to_apply: &mut Vec<Vec<f64>>,
|
|
1329
|
+
) {
|
|
1330
|
+
for index in 0..random_seeds.len() {
|
|
1331
|
+
let mut rng = StdRng::seed_from_u64(random_seeds[index] as u64);
|
|
1332
|
+
for array_index in 0..wins.len() {
|
|
1333
|
+
let random_num = rng.gen_range(-100..=100) as f64;
|
|
1334
|
+
random_weights_to_apply[index][array_index] =
|
|
1335
|
+
1.0 + ((random_num) / 100.0) * random_weights[index];
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
for index in 0..wins.len() {
|
|
1339
|
+
let mut total_weight = 0.0;
|
|
1340
|
+
for norm_index in 0..amps.len() {
|
|
1341
|
+
let mut weight = amps[norm_index]
|
|
1342
|
+
* (1.0
|
|
1343
|
+
+ 20000.0
|
|
1344
|
+
* (1.0 / ((stds[norm_index] * (2.0 * 3.14)).sqrt()))
|
|
1345
|
+
* ((2.71_f64).powf(
|
|
1346
|
+
-0.5 * ((wins[index] - mus[norm_index]) / stds[norm_index]).powf(2.0),
|
|
1347
|
+
)));
|
|
1348
|
+
|
|
1349
|
+
for param_index in 0..(params.len() / 3) {
|
|
1350
|
+
if let Some(apply_norm_indexes) = apply_parms.get(param_index) {
|
|
1351
|
+
if apply_norm_indexes
|
|
1352
|
+
.binary_search(&(norm_index as u16))
|
|
1353
|
+
.is_ok()
|
|
1354
|
+
&& wins[index] >= params[3 * param_index]
|
|
1355
|
+
&& wins[index] <= params[3 * param_index + 1]
|
|
1356
|
+
{
|
|
1357
|
+
weight *= params[3 * param_index + 2];
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
for rand_index in 0..random_apply_params.len() {
|
|
1362
|
+
if random_apply_params[rand_index].contains(&norm_index) {
|
|
1363
|
+
weight *= random_weights_to_apply[rand_index][index];
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
total_weight += weight;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
weights[index] = total_weight;
|
|
1370
|
+
}
|
|
1371
|
+
// let mut rtp = 0.0;
|
|
1372
|
+
// for index in 0..wins.len(){
|
|
1373
|
+
// rtp += wins[index]*weights[index]
|
|
1374
|
+
// }
|
|
1375
|
+
// print!("{}",rtp)
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
fn breed_pigs(pos_pig: &Pig, neg_pig: &Pig, pig_heaven: &PigHeaven) -> Pig {
|
|
1379
|
+
let mut added_params = 0;
|
|
1380
|
+
let mut new_pig_parms: Vec<f64> = Vec::new();
|
|
1381
|
+
let mut new_pig_apply_parms: Vec<Vec<u16>> = Vec::new();
|
|
1382
|
+
|
|
1383
|
+
for param_index in 0..(pos_pig.params.len() / 3) {
|
|
1384
|
+
new_pig_parms.extend(Vec::from([
|
|
1385
|
+
pos_pig.params.get(3 * param_index).expect("error"),
|
|
1386
|
+
pos_pig.params.get(3 * param_index + 1).expect("error"),
|
|
1387
|
+
pos_pig.params.get(3 * param_index + 2).expect("error"),
|
|
1388
|
+
]));
|
|
1389
|
+
new_pig_apply_parms.insert(
|
|
1390
|
+
added_params.clone(),
|
|
1391
|
+
(0..pos_pig.mus.len()).map(|i| i as u16).collect(),
|
|
1392
|
+
);
|
|
1393
|
+
added_params += 1;
|
|
1394
|
+
}
|
|
1395
|
+
for param_index in 0..(neg_pig.params.len() / 3) {
|
|
1396
|
+
new_pig_parms.extend(Vec::from([
|
|
1397
|
+
neg_pig.params.get(3 * param_index).expect("error"),
|
|
1398
|
+
neg_pig.params.get(3 * param_index + 1).expect("error"),
|
|
1399
|
+
neg_pig.params.get(3 * param_index + 2).expect("error"),
|
|
1400
|
+
]));
|
|
1401
|
+
new_pig_apply_parms.insert(
|
|
1402
|
+
added_params.clone(),
|
|
1403
|
+
(pos_pig.mus.len()..(pos_pig.mus.len() + neg_pig.mus.len()))
|
|
1404
|
+
.map(|i| i as u16)
|
|
1405
|
+
.collect(),
|
|
1406
|
+
);
|
|
1407
|
+
added_params += 1;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
let (
|
|
1411
|
+
rtp,
|
|
1412
|
+
sum_dist,
|
|
1413
|
+
new_amps,
|
|
1414
|
+
new_stds,
|
|
1415
|
+
new_mus,
|
|
1416
|
+
new_random_seeds,
|
|
1417
|
+
new_random_weights,
|
|
1418
|
+
new_random_apply_params,
|
|
1419
|
+
) = combine_distributions(pos_pig, neg_pig, pig_heaven);
|
|
1420
|
+
|
|
1421
|
+
return Pig {
|
|
1422
|
+
amps: new_amps,
|
|
1423
|
+
mus: new_mus,
|
|
1424
|
+
stds: new_stds,
|
|
1425
|
+
params: new_pig_parms,
|
|
1426
|
+
apply_parms: new_pig_apply_parms,
|
|
1427
|
+
rtp,
|
|
1428
|
+
sum_dist: sum_dist,
|
|
1429
|
+
random_seeds: new_random_seeds,
|
|
1430
|
+
random_weights: new_random_weights,
|
|
1431
|
+
random_apply_params: new_random_apply_params,
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
fn combine_distributions(
|
|
1436
|
+
pos_pig: &Pig,
|
|
1437
|
+
neg_pig: &Pig,
|
|
1438
|
+
pig_heaven: &PigHeaven,
|
|
1439
|
+
) -> (
|
|
1440
|
+
f64,
|
|
1441
|
+
f64,
|
|
1442
|
+
Vec<f64>,
|
|
1443
|
+
Vec<f64>,
|
|
1444
|
+
Vec<f64>,
|
|
1445
|
+
Vec<u32>,
|
|
1446
|
+
Vec<f64>,
|
|
1447
|
+
Vec<Vec<usize>>,
|
|
1448
|
+
) {
|
|
1449
|
+
// @Rob these vectors could probably be declared before hand
|
|
1450
|
+
let weight = (pig_heaven.avg_win - neg_pig.rtp) / (pos_pig.rtp - neg_pig.rtp);
|
|
1451
|
+
let mut new_amps: Vec<f64> = Vec::new();
|
|
1452
|
+
let mut new_stds: Vec<f64> = Vec::new();
|
|
1453
|
+
let mut new_mus: Vec<f64> = Vec::new();
|
|
1454
|
+
|
|
1455
|
+
new_amps.extend(
|
|
1456
|
+
pos_pig
|
|
1457
|
+
.amps
|
|
1458
|
+
.iter()
|
|
1459
|
+
.map(|amp| amp * weight / pos_pig.sum_dist),
|
|
1460
|
+
);
|
|
1461
|
+
new_amps.extend(
|
|
1462
|
+
neg_pig
|
|
1463
|
+
.amps
|
|
1464
|
+
.iter()
|
|
1465
|
+
.map(|amp| amp * (1.0 - weight) / neg_pig.sum_dist),
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
new_stds.extend(&pos_pig.stds);
|
|
1469
|
+
new_stds.extend(&neg_pig.stds);
|
|
1470
|
+
|
|
1471
|
+
new_mus.extend(&pos_pig.mus);
|
|
1472
|
+
new_mus.extend(&neg_pig.mus);
|
|
1473
|
+
|
|
1474
|
+
let rtp = weight * pos_pig.rtp + (1.0 - weight) * neg_pig.rtp;
|
|
1475
|
+
|
|
1476
|
+
let new_random_weights: Vec<f64> = vec![pos_pig.random_weights[0], neg_pig.random_weights[0]];
|
|
1477
|
+
let new_random_seeds: Vec<u32> = vec![pos_pig.random_seeds[0], neg_pig.random_seeds[0]];
|
|
1478
|
+
let new_random_apply_parms: Vec<Vec<usize>> = Vec::from([
|
|
1479
|
+
(0..pos_pig.amps.len()).collect(),
|
|
1480
|
+
(pos_pig.amps.len()..pos_pig.amps.len() + neg_pig.amps.len()).collect(),
|
|
1481
|
+
]);
|
|
1482
|
+
|
|
1483
|
+
(
|
|
1484
|
+
rtp,
|
|
1485
|
+
1.0,
|
|
1486
|
+
new_amps,
|
|
1487
|
+
new_stds,
|
|
1488
|
+
new_mus,
|
|
1489
|
+
new_random_seeds,
|
|
1490
|
+
new_random_weights,
|
|
1491
|
+
new_random_apply_parms,
|
|
1492
|
+
)
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
fn parse_scale_factor(scale_factor: &str) -> ScaleFactor {
|
|
1496
|
+
if scale_factor.ends_with('r') {
|
|
1497
|
+
let value_without_r: &str = scale_factor.trim_end_matches('r');
|
|
1498
|
+
if let Ok(factor) = value_without_r.parse::<f64>() {
|
|
1499
|
+
return ScaleFactor::FactorR(factor);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
if let Ok(factor) = scale_factor.parse::<f64>() {
|
|
1504
|
+
ScaleFactor::Factor(factor)
|
|
1505
|
+
} else {
|
|
1506
|
+
// You might want to handle the error case here, e.g., return a default value.
|
|
1507
|
+
// For simplicity, I'm returning ScaleFactor::Factor(1.0) as a default.
|
|
1508
|
+
ScaleFactor::Factor(1.0)
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
fn run_simulation(
|
|
1513
|
+
wins: &Array1<f64>,
|
|
1514
|
+
weights: &Array1<f64>,
|
|
1515
|
+
spins: u32,
|
|
1516
|
+
trials: u32,
|
|
1517
|
+
bet: f64,
|
|
1518
|
+
test_spins: &Vec<u32>,
|
|
1519
|
+
banks: &mut Array1<f64>,
|
|
1520
|
+
test_spins_weights: &Vec<f64>,
|
|
1521
|
+
pmb_rtp: f64,
|
|
1522
|
+
) -> f64 {
|
|
1523
|
+
let num_test_spins = test_spins.len();
|
|
1524
|
+
let mut success: Vec<f64> = vec![0.0; num_test_spins];
|
|
1525
|
+
|
|
1526
|
+
let mut rng = thread_rng();
|
|
1527
|
+
let banks_slice = banks.as_slice_mut().unwrap();
|
|
1528
|
+
let weighted_index = WeightedIndex::new(weights).expect("Invalid weights");
|
|
1529
|
+
|
|
1530
|
+
for trial in 0..trials {
|
|
1531
|
+
let chosen_wins_indices = (0..spins)
|
|
1532
|
+
.map(|_| weighted_index.sample(&mut rng))
|
|
1533
|
+
.collect::<Vec<usize>>();
|
|
1534
|
+
|
|
1535
|
+
for (i, &index) in chosen_wins_indices.iter().enumerate() {
|
|
1536
|
+
banks_slice[(trial * spins + i as u32) as usize] = wins[index];
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
for trial in 0..trials {
|
|
1540
|
+
for (spin_index, &test_spin) in test_spins.iter().enumerate() {
|
|
1541
|
+
let bank_slice = &banks.slice(s![
|
|
1542
|
+
(trial as usize) * (spins as usize)..(trial * spins + test_spin) as usize
|
|
1543
|
+
]);
|
|
1544
|
+
let total_bank: f64 = bank_slice.iter().sum();
|
|
1545
|
+
if (total_bank / (test_spin as f64 * bet)) >= pmb_rtp {
|
|
1546
|
+
success[spin_index] += 1.0;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
let mut final_score = 0.0;
|
|
1552
|
+
for i in 0..success.len() {
|
|
1553
|
+
final_score += success[i] / (trials as f64) * test_spins_weights[i];
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
final_score
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
fn run_enhanced_simulation(
|
|
1560
|
+
wins: &Array1<f64>,
|
|
1561
|
+
weights: &Array1<f64>,
|
|
1562
|
+
spins: usize,
|
|
1563
|
+
trials: usize,
|
|
1564
|
+
bet: f64,
|
|
1565
|
+
test_spins: &Vec<u32>,
|
|
1566
|
+
pmb_rtp: f64,
|
|
1567
|
+
) -> Vec<f64> {
|
|
1568
|
+
let num_spins = test_spins[test_spins.len() - 1usize] as usize;
|
|
1569
|
+
let total_spin_trials = num_spins * trials;
|
|
1570
|
+
|
|
1571
|
+
let success: Vec<f64> = (0..num_spins)
|
|
1572
|
+
.into_par_iter()
|
|
1573
|
+
.map(|spin_index| {
|
|
1574
|
+
let mut spin_success = 0.0;
|
|
1575
|
+
|
|
1576
|
+
for _ in 0..trials {
|
|
1577
|
+
let mut rng = thread_rng();
|
|
1578
|
+
let mut banks: Array1<f64> = Array1::zeros(spins);
|
|
1579
|
+
let banks_slice = banks.as_slice_mut().unwrap();
|
|
1580
|
+
let weighted_index = WeightedIndex::new(weights).expect("Invalid weights");
|
|
1581
|
+
|
|
1582
|
+
for i in 0..spins {
|
|
1583
|
+
banks_slice[i] = wins[weighted_index.sample(&mut rng)];
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
let bank_slice = &banks.slice(s![..(spin_index + 1) as usize]);
|
|
1587
|
+
let total_bank: f64 = bank_slice.iter().sum();
|
|
1588
|
+
if (total_bank / ((spin_index + 1) as f64 * bet)) >= pmb_rtp {
|
|
1589
|
+
spin_success += 1.0;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
spin_success
|
|
1594
|
+
})
|
|
1595
|
+
.collect();
|
|
1596
|
+
|
|
1597
|
+
let total_trials = trials as f64;
|
|
1598
|
+
success.iter().map(|s| s / total_trials).collect()
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
#[derive(Copy)]
|
|
1602
|
+
pub struct F64Wrapper(f64);
|
|
1603
|
+
|
|
1604
|
+
impl F64Wrapper {
|
|
1605
|
+
// Method to convert bits to an f64 value
|
|
1606
|
+
fn from_bits(bits: u64) -> f64 {
|
|
1607
|
+
f64::from_bits(bits)
|
|
1608
|
+
}
|
|
1609
|
+
fn new(val: f64) -> Self {
|
|
1610
|
+
F64Wrapper(val)
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
fn value(&self) -> f64 {
|
|
1614
|
+
self.0
|
|
1615
|
+
}
|
|
1616
|
+
fn copy(&self) -> F64Wrapper {
|
|
1617
|
+
F64Wrapper(self.0)
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
impl PartialEq for F64Wrapper {
|
|
1622
|
+
fn eq(&self, other: &Self) -> bool {
|
|
1623
|
+
let diff = (self.0 - other.0).abs() < 0.00000000001;
|
|
1624
|
+
diff
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
impl Eq for F64Wrapper {}
|
|
1629
|
+
|
|
1630
|
+
impl PartialOrd for F64Wrapper {
|
|
1631
|
+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
1632
|
+
self.0.partial_cmp(&other.0)
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
impl Clone for F64Wrapper {
|
|
1637
|
+
fn clone(&self) -> F64Wrapper {
|
|
1638
|
+
F64Wrapper(self.0)
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
impl Ord for F64Wrapper {
|
|
1643
|
+
fn cmp(&self, other: &Self) -> Ordering {
|
|
1644
|
+
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
impl Hash for F64Wrapper {
|
|
1649
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
1650
|
+
// Convert the f64 to a bit representation and hash it
|
|
1651
|
+
let bits: u64 = self.0.to_bits();
|
|
1652
|
+
bits.hash(state);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
struct ShowPig {
|
|
1657
|
+
pub pig_indexes: Vec<usize>,
|
|
1658
|
+
pub success_score: f64,
|
|
1659
|
+
}
|