@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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +33 -0
  2. package/.turbo/turbo-typecheck.log +4 -0
  3. package/CHANGELOG.md +7 -0
  4. package/README.md +8 -0
  5. package/dist/index.d.mts +1306 -0
  6. package/dist/index.d.ts +1306 -0
  7. package/dist/index.js +2929 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +2874 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/lib/zstd.exe +0 -0
  12. package/dist/optimizer-rust/Cargo.toml +19 -0
  13. package/dist/optimizer-rust/src/exes.rs +154 -0
  14. package/dist/optimizer-rust/src/main.rs +1659 -0
  15. package/index.ts +205 -0
  16. package/lib/zstd.exe +0 -0
  17. package/optimizer-rust/Cargo.toml +19 -0
  18. package/optimizer-rust/src/exes.rs +154 -0
  19. package/optimizer-rust/src/main.rs +1659 -0
  20. package/package.json +33 -0
  21. package/src/Board.ts +527 -0
  22. package/src/Book.ts +83 -0
  23. package/src/GameConfig.ts +148 -0
  24. package/src/GameMode.ts +86 -0
  25. package/src/GameState.ts +272 -0
  26. package/src/GameSymbol.ts +61 -0
  27. package/src/ReelGenerator.ts +589 -0
  28. package/src/ResultSet.ts +207 -0
  29. package/src/Simulation.ts +625 -0
  30. package/src/SlotGame.ts +117 -0
  31. package/src/Wallet.ts +203 -0
  32. package/src/WinType.ts +102 -0
  33. package/src/analysis/index.ts +198 -0
  34. package/src/analysis/utils.ts +128 -0
  35. package/src/optimizer/OptimizationConditions.ts +99 -0
  36. package/src/optimizer/OptimizationParameters.ts +46 -0
  37. package/src/optimizer/OptimizationScaling.ts +18 -0
  38. package/src/optimizer/index.ts +142 -0
  39. package/src/utils/math-config.ts +109 -0
  40. package/src/utils/setup-file.ts +36 -0
  41. package/src/utils/zstd.ts +28 -0
  42. package/src/winTypes/ClusterWinType.ts +3 -0
  43. package/src/winTypes/LinesWinType.ts +208 -0
  44. package/src/winTypes/ManywaysWinType.ts +3 -0
  45. package/tsconfig.json +19 -0
  46. 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
+ }