|
|
|
@ -1,13 +1,23 @@
|
|
|
|
|
use std::{fs::File, path::PathBuf, sync::LazyLock};
|
|
|
|
|
use std::{
|
|
|
|
|
cell::RefCell,
|
|
|
|
|
fs::File,
|
|
|
|
|
path::PathBuf,
|
|
|
|
|
sync::{
|
|
|
|
|
LazyLock,
|
|
|
|
|
atomic::{AtomicU32, Ordering},
|
|
|
|
|
},
|
|
|
|
|
time::Instant,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use az::{Az, WrappingAs};
|
|
|
|
|
use battlesnake::types::{Coord, Direction, wire::Request};
|
|
|
|
|
use bytemuck::cast_slice;
|
|
|
|
|
use clap::Parser;
|
|
|
|
|
use flamer::flame;
|
|
|
|
|
//use flamer::flame;
|
|
|
|
|
use hashbrown::HashMap;
|
|
|
|
|
use log::{debug, error, info, trace, warn};
|
|
|
|
|
use log::{debug, error, info, trace};
|
|
|
|
|
use memmap2::{Mmap, MmapOptions};
|
|
|
|
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
|
|
|
|
|
|
|
|
|
type DynError = Box<dyn std::error::Error>;
|
|
|
|
|
|
|
|
|
@ -21,9 +31,12 @@ struct Args {
|
|
|
|
|
#[arg()]
|
|
|
|
|
/// The file to read the requests from
|
|
|
|
|
file: PathBuf,
|
|
|
|
|
/// Use multiple cores to speed up search
|
|
|
|
|
#[arg(long, short)]
|
|
|
|
|
parallel: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn main() -> Result<(), DynError> {
|
|
|
|
|
env_logger::init();
|
|
|
|
|
|
|
|
|
@ -39,38 +52,83 @@ fn main() -> Result<(), DynError> {
|
|
|
|
|
let _ = *CHAIN;
|
|
|
|
|
|
|
|
|
|
let mut rand = SeedRand::new(0);
|
|
|
|
|
let prepared = PreparedGame::prepare(&requests);
|
|
|
|
|
|
|
|
|
|
if let Some(seed) = args.seed {
|
|
|
|
|
if validate(&mut rand, &requests, seed, true) {
|
|
|
|
|
info!("Validating using old method");
|
|
|
|
|
let start = Instant::now();
|
|
|
|
|
let valid = validate(&mut rand, &requests, seed, true);
|
|
|
|
|
let elapsed = start.elapsed();
|
|
|
|
|
info!("Validation took {elapsed:?}");
|
|
|
|
|
if valid {
|
|
|
|
|
println!("{seed} is the correct seed for this game");
|
|
|
|
|
} else {
|
|
|
|
|
println!("{seed} is not the correct seed for this game");
|
|
|
|
|
}
|
|
|
|
|
info!("Validating using prepared method");
|
|
|
|
|
let start = Instant::now();
|
|
|
|
|
let valid = prepared.validate(&mut rand, seed, true);
|
|
|
|
|
let elapsed = start.elapsed();
|
|
|
|
|
info!("Validation took {elapsed:?}");
|
|
|
|
|
if valid {
|
|
|
|
|
println!("{seed} is the correct seed for this game");
|
|
|
|
|
} else {
|
|
|
|
|
println!("{seed} is not the correct seed for this game");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for seed in 1..i32::MAX {
|
|
|
|
|
if seed == 100 {
|
|
|
|
|
break;
|
|
|
|
|
info!("Trying every seed from 1 to {}", i32::MAX);
|
|
|
|
|
let start = Instant::now();
|
|
|
|
|
if args.parallel {
|
|
|
|
|
thread_local! {
|
|
|
|
|
static RAND: RefCell<SeedRand> = RefCell::new(SeedRand::new(0));
|
|
|
|
|
}
|
|
|
|
|
if seed % 1_000_000 == 0 {
|
|
|
|
|
debug!("Checking {seed:>10}");
|
|
|
|
|
}
|
|
|
|
|
if validate(&mut rand, &requests, i64::from(seed), false) {
|
|
|
|
|
let tried = AtomicU32::new(0);
|
|
|
|
|
let seed = (1..i32::MAX).into_par_iter().find_any(|seed| {
|
|
|
|
|
tried.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
RAND.with_borrow_mut(|rand| prepared.validate(rand, i64::from(*seed), false))
|
|
|
|
|
});
|
|
|
|
|
let elapsed = start.elapsed();
|
|
|
|
|
let tried = tried.load(Ordering::Relaxed);
|
|
|
|
|
info!(
|
|
|
|
|
"Trying {tried} seeds took {elapsed:?} ({:?} / seed)",
|
|
|
|
|
elapsed / tried.az()
|
|
|
|
|
);
|
|
|
|
|
if let Some(seed) = seed {
|
|
|
|
|
println!("The correct seed is {seed}");
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
println!("No valid seed found");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let seed = (1..i32::MAX).find(|seed| {
|
|
|
|
|
if seed % 1_000_000 == 0 {
|
|
|
|
|
debug!("Checking {seed:>10}");
|
|
|
|
|
}
|
|
|
|
|
prepared.validate(&mut rand, i64::from(*seed), false)
|
|
|
|
|
});
|
|
|
|
|
let elapsed = start.elapsed();
|
|
|
|
|
let tried = seed.unwrap_or(i32::MAX);
|
|
|
|
|
info!(
|
|
|
|
|
"Trying {tried} seeds took {elapsed:?} ({:?} / seed)",
|
|
|
|
|
elapsed / tried.az()
|
|
|
|
|
);
|
|
|
|
|
if let Some(seed) = seed {
|
|
|
|
|
println!("The correct seed is {seed}");
|
|
|
|
|
} else {
|
|
|
|
|
println!("No valid seed found");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flame::dump_html(File::create("flamegraph.html")?)?;
|
|
|
|
|
//flame::dump_html(File::create("flamegraph.html")?)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_lines)]
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: bool) -> bool {
|
|
|
|
|
if log {
|
|
|
|
|
info!("Checking starting positions");
|
|
|
|
|
debug!("Checking starting positions");
|
|
|
|
|
}
|
|
|
|
|
let request = &requests[0];
|
|
|
|
|
let rand = get_rand(input_rand, seed, 0);
|
|
|
|
@ -90,12 +148,8 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo
|
|
|
|
|
Coord { x: mx, y: md },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
rand.shuffle(corner_points.len(), |i, j| {
|
|
|
|
|
corner_points.swap(i, j);
|
|
|
|
|
});
|
|
|
|
|
rand.shuffle(cardinal_points.len(), |i, j| {
|
|
|
|
|
cardinal_points.swap(i, j);
|
|
|
|
|
});
|
|
|
|
|
rand.shuffle(&mut corner_points);
|
|
|
|
|
rand.shuffle(&mut cardinal_points);
|
|
|
|
|
|
|
|
|
|
let mut start_points = Vec::with_capacity(corner_points.len() + cardinal_points.len());
|
|
|
|
|
if rand.int_n(2) == 0 {
|
|
|
|
@ -106,12 +160,12 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo
|
|
|
|
|
start_points.extend_from_slice(&corner_points);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i in 0..request.board.snakes.len() {
|
|
|
|
|
if start_points[i] != request.board.snakes[i].head {
|
|
|
|
|
for (snake, start_point) in request.board.snakes.iter().zip(start_points.iter()) {
|
|
|
|
|
if *start_point != snake.head {
|
|
|
|
|
if log {
|
|
|
|
|
error!(
|
|
|
|
|
"Starting position is not the same: {} != {}",
|
|
|
|
|
start_points[i], request.board.snakes[i].head
|
|
|
|
|
start_point, snake.head
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
@ -119,22 +173,16 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if log {
|
|
|
|
|
info!("Checking food placement");
|
|
|
|
|
debug!("Checking food placement");
|
|
|
|
|
}
|
|
|
|
|
for i in 1..requests.len() {
|
|
|
|
|
let previous_request = &requests[i - 1];
|
|
|
|
|
let request = &requests[i];
|
|
|
|
|
if previous_request.turn + 1 != request.turn {
|
|
|
|
|
if log {
|
|
|
|
|
warn!(
|
|
|
|
|
"nonconsecutive requests: {} + 1 != {}",
|
|
|
|
|
previous_request.turn, request.turn
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if log {
|
|
|
|
|
info!("Checking turn {}", request.turn);
|
|
|
|
|
debug!("Checking turn {}", request.turn);
|
|
|
|
|
}
|
|
|
|
|
let rand = get_rand(input_rand, seed, i64::from(request.turn - 1));
|
|
|
|
|
let mut forged_request = request.clone();
|
|
|
|
@ -160,9 +208,6 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo
|
|
|
|
|
&forged_request,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if log {
|
|
|
|
|
debug!("snake head: {:?}", request.board.snakes[0].head);
|
|
|
|
|
}
|
|
|
|
|
if forged_request.board.food != request.board.food {
|
|
|
|
|
let forged: Vec<_> = forged_request
|
|
|
|
|
.board
|
|
|
|
@ -190,14 +235,223 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct PreparedGame {
|
|
|
|
|
corners: [Option<u8>; 4],
|
|
|
|
|
cardinals: [Option<u8>; 4],
|
|
|
|
|
corner_first: bool,
|
|
|
|
|
food_spawns: Vec<FoodSpawn>,
|
|
|
|
|
food_chance: u8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
|
|
enum FoodSpawn {
|
|
|
|
|
/// No food was spawned
|
|
|
|
|
None,
|
|
|
|
|
/// Some food was spawned because it would go under `min_food` otherwise
|
|
|
|
|
Forced { selected: u16, free_spots: u16 },
|
|
|
|
|
/// Some food was spawned by chance
|
|
|
|
|
Random { selected: u16, free_spots: u16 },
|
|
|
|
|
/// No food was spawned because there is no space on the board
|
|
|
|
|
Blocked,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PreparedGame {
|
|
|
|
|
fn prepare(requests: &[Request]) -> Self {
|
|
|
|
|
debug!("Preparing initial board state");
|
|
|
|
|
let request = &requests[0];
|
|
|
|
|
assert_eq!(request.turn, 0);
|
|
|
|
|
let mn = 1;
|
|
|
|
|
let md = (request.board.width - 1) / 2;
|
|
|
|
|
let mx = request.board.width - 2;
|
|
|
|
|
let corners = [
|
|
|
|
|
Coord { x: mn, y: mn },
|
|
|
|
|
Coord { x: mn, y: mx },
|
|
|
|
|
Coord { x: mx, y: mn },
|
|
|
|
|
Coord { x: mx, y: mx },
|
|
|
|
|
];
|
|
|
|
|
let cardinals = [
|
|
|
|
|
Coord { x: mn, y: md },
|
|
|
|
|
Coord { x: md, y: mn },
|
|
|
|
|
Coord { x: md, y: mx },
|
|
|
|
|
Coord { x: mx, y: md },
|
|
|
|
|
];
|
|
|
|
|
let corner_first = corners.contains(&request.board.snakes[0].head);
|
|
|
|
|
let corners = corners.map(|start| {
|
|
|
|
|
request
|
|
|
|
|
.board
|
|
|
|
|
.snakes
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|snake| snake.head == start)
|
|
|
|
|
.map(|pos| (pos % 4).az())
|
|
|
|
|
});
|
|
|
|
|
let cardinals = cardinals.map(|start| {
|
|
|
|
|
request
|
|
|
|
|
.board
|
|
|
|
|
.snakes
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|snake| snake.head == start)
|
|
|
|
|
.map(|pos| (pos % 4).az())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
debug!("Preparing food placement");
|
|
|
|
|
let food_chance = request.game.ruleset.settings.food_spawn_chance;
|
|
|
|
|
let food_min = request.game.ruleset.settings.minimum_food;
|
|
|
|
|
|
|
|
|
|
let food_spawns = requests
|
|
|
|
|
.windows(2)
|
|
|
|
|
.filter_map(|window| {
|
|
|
|
|
if let [previous, next] = window {
|
|
|
|
|
if previous.turn + 1 == next.turn {
|
|
|
|
|
Some((previous, next))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
error!("Windows didn't return 2 requests: {requests:#?}");
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.map(|(previous, next)| {
|
|
|
|
|
let mut tmp = next.clone();
|
|
|
|
|
tmp.board.food.clone_from(&previous.board.food);
|
|
|
|
|
let heads: Vec<_> = tmp.board.snakes.iter().map(|snake| snake.head).collect();
|
|
|
|
|
tmp.board.food.retain(|coord| !heads.contains(coord));
|
|
|
|
|
let free_spots = get_unoccupied_points(&tmp);
|
|
|
|
|
|
|
|
|
|
if free_spots.is_empty() {
|
|
|
|
|
return FoodSpawn::Blocked;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let diff: Vec<_> = next
|
|
|
|
|
.board
|
|
|
|
|
.food
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|next| !previous.board.food.contains(next))
|
|
|
|
|
.collect();
|
|
|
|
|
if diff.is_empty() {
|
|
|
|
|
// We didn't spawn any food. It was only eaten
|
|
|
|
|
return FoodSpawn::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let selected = free_spots
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|spot| spot == diff[0])
|
|
|
|
|
.unwrap()
|
|
|
|
|
.az();
|
|
|
|
|
let free_spots = free_spots.len().az();
|
|
|
|
|
|
|
|
|
|
if next.board.food.len() == usize::from(food_min) {
|
|
|
|
|
FoodSpawn::Forced {
|
|
|
|
|
selected,
|
|
|
|
|
free_spots,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
FoodSpawn::Random {
|
|
|
|
|
selected,
|
|
|
|
|
free_spots,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
corners,
|
|
|
|
|
cardinals,
|
|
|
|
|
corner_first,
|
|
|
|
|
food_spawns,
|
|
|
|
|
food_chance,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate(&self, input_rand: &mut SeedRand, seed: i64, log: bool) -> bool {
|
|
|
|
|
if log {
|
|
|
|
|
debug!("Checking Start positions");
|
|
|
|
|
}
|
|
|
|
|
let mut corners = [None; 4];
|
|
|
|
|
for (a, b) in self.corners.iter().zip(corners.iter_mut()) {
|
|
|
|
|
*b = a.map(usize::from);
|
|
|
|
|
}
|
|
|
|
|
let mut cardinals = [None; 4];
|
|
|
|
|
for (a, b) in self.cardinals.iter().zip(cardinals.iter_mut()) {
|
|
|
|
|
*b = a.map(usize::from);
|
|
|
|
|
}
|
|
|
|
|
let rand = get_rand(input_rand, seed, 0);
|
|
|
|
|
if !rand.shuffle_check(&mut corners) {
|
|
|
|
|
if log {
|
|
|
|
|
error!("Corners are shuffled wrong");
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if !rand.shuffle_check(&mut cardinals) {
|
|
|
|
|
if log {
|
|
|
|
|
error!("Cardinals are shuffled wrong");
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if rand.int_n(2) == usize::from(self.corner_first) {
|
|
|
|
|
if log {
|
|
|
|
|
error!("Corners and cardinals are flipped: {}", self.corner_first);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.food_spawns.iter().enumerate().all(|(turn, spawn)| {
|
|
|
|
|
if log {
|
|
|
|
|
debug!("Checking turn {turn}");
|
|
|
|
|
}
|
|
|
|
|
let rand = get_rand(input_rand, seed, (turn).az());
|
|
|
|
|
|
|
|
|
|
match spawn {
|
|
|
|
|
spawn @ (FoodSpawn::Forced {
|
|
|
|
|
selected,
|
|
|
|
|
free_spots,
|
|
|
|
|
}
|
|
|
|
|
| FoodSpawn::Random {
|
|
|
|
|
selected,
|
|
|
|
|
free_spots,
|
|
|
|
|
}) => {
|
|
|
|
|
if matches!(spawn, FoodSpawn::Random { .. })
|
|
|
|
|
&& 100 - rand.int_n(100) >= usize::from(self.food_chance)
|
|
|
|
|
{
|
|
|
|
|
if log {
|
|
|
|
|
error!("Food not spawned, when some should have been spawned");
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut foods = vec![None; usize::from(*free_spots)];
|
|
|
|
|
foods[usize::from(*selected)] = Some(0);
|
|
|
|
|
let correct = rand.shuffle_check(&mut foods);
|
|
|
|
|
if log && !correct {
|
|
|
|
|
error!("Wrong food selected: {foods:?}");
|
|
|
|
|
}
|
|
|
|
|
correct
|
|
|
|
|
}
|
|
|
|
|
FoodSpawn::None => {
|
|
|
|
|
let correct = 100 - rand.int_n(100) >= usize::from(self.food_chance);
|
|
|
|
|
if log && !correct {
|
|
|
|
|
error!("Food spawned, when none should have been spawned");
|
|
|
|
|
}
|
|
|
|
|
correct
|
|
|
|
|
}
|
|
|
|
|
FoodSpawn::Blocked => {
|
|
|
|
|
// We can't get any info if there isn't even a chance for food
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn get_rand(rand: &mut SeedRand, seed: i64, turn: i64) -> &mut SeedRand {
|
|
|
|
|
rand.rand.seed(seed + turn);
|
|
|
|
|
rand
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn check_food_needing_placement(rand: &mut impl Rand, request: &Request) -> usize {
|
|
|
|
|
let min_food = request.game.ruleset.settings.minimum_food as usize;
|
|
|
|
|
let food_spawn_chance = request.game.ruleset.settings.food_spawn_chance as usize;
|
|
|
|
@ -213,13 +467,13 @@ fn check_food_needing_placement(rand: &mut impl Rand, request: &Request) -> usiz
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn place_food_randomly(rand: &mut impl Rand, n: usize, request: &Request) -> Vec<Coord> {
|
|
|
|
|
let unoccupied_points = get_unoccupied_points(request);
|
|
|
|
|
place_food_randomly_at_positions(rand, n, unoccupied_points)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn place_food_randomly_at_positions(
|
|
|
|
|
rand: &mut impl Rand,
|
|
|
|
|
n: usize,
|
|
|
|
@ -227,9 +481,7 @@ fn place_food_randomly_at_positions(
|
|
|
|
|
) -> Vec<Coord> {
|
|
|
|
|
let n = n.min(positions.len());
|
|
|
|
|
|
|
|
|
|
rand.shuffle(positions.len(), |i, j| {
|
|
|
|
|
positions.swap(i, j);
|
|
|
|
|
});
|
|
|
|
|
rand.shuffle(&mut positions);
|
|
|
|
|
|
|
|
|
|
let mut out = Vec::new();
|
|
|
|
|
let mut i = 0;
|
|
|
|
@ -240,7 +492,7 @@ fn place_food_randomly_at_positions(
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn get_unoccupied_points(request: &Request) -> Vec<Coord> {
|
|
|
|
|
let possible_moves: Vec<_> = request
|
|
|
|
|
.board
|
|
|
|
@ -275,7 +527,10 @@ trait Rand {
|
|
|
|
|
fn range(&mut self, min: usize, max: usize) -> usize;
|
|
|
|
|
/// shuffle an array of n values using the swap function
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
fn shuffle(&mut self, n: usize, swap: impl FnMut(usize, usize));
|
|
|
|
|
fn shuffle<T>(&mut self, data: &mut [T]);
|
|
|
|
|
/// checks shuffle an array of n values using the swap function
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
fn shuffle_check(&mut self, data: &mut [Option<usize>]) -> bool;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
|
|
@ -284,7 +539,7 @@ struct SeedRand {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SeedRand {
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn new(seed: i64) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
rand: GoRand::new(GoSource::new(seed)),
|
|
|
|
@ -293,19 +548,23 @@ impl SeedRand {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Rand for SeedRand {
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int_n(&mut self, n: usize) -> usize {
|
|
|
|
|
self.rand.int_n(n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn range(&mut self, min: usize, max: usize) -> usize {
|
|
|
|
|
self.rand.int_n(max - min + 1) + min
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
fn shuffle(&mut self, n: usize, swap: impl FnMut(usize, usize)) {
|
|
|
|
|
self.rand.shuffle(n, swap);
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn shuffle<T>(&mut self, data: &mut [T]) {
|
|
|
|
|
self.rand.shuffle(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn shuffle_check(&mut self, data: &mut [Option<usize>]) -> bool {
|
|
|
|
|
self.rand.shuffle_check(data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -315,26 +574,26 @@ struct GoRand {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GoRand {
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
const fn new(src: GoSource) -> Self {
|
|
|
|
|
Self { src }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Seed uses the provided seed value to initialize the generator to a deterministic state.
|
|
|
|
|
// Seed should not be called concurrently with any other [Rand] method.
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn seed(&mut self, seed: i64) {
|
|
|
|
|
self.src.seed(seed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// returns a non-negative pseudo-random 63-bit integer
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int63(&mut self) -> u64 {
|
|
|
|
|
self.src.int63()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// returns a non-negative pseudo-random 63-bit integer
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int31(&mut self) -> u32 {
|
|
|
|
|
(self.src.int63() >> 32) as u32
|
|
|
|
|
}
|
|
|
|
@ -343,7 +602,7 @@ impl GoRand {
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
/// if n <= 0
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int63_n(&mut self, n: u64) -> u64 {
|
|
|
|
|
assert!((n > 0), "invalid argument to int63_n");
|
|
|
|
|
if n.is_power_of_two() {
|
|
|
|
@ -362,7 +621,7 @@ impl GoRand {
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
/// if n <= 0
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int31_n(&mut self, n: u32) -> u32 {
|
|
|
|
|
assert!((n > 0), "invalid argument to in32_n");
|
|
|
|
|
if n.is_power_of_two() {
|
|
|
|
@ -377,12 +636,12 @@ impl GoRand {
|
|
|
|
|
v % n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn uint32(&mut self) -> u32 {
|
|
|
|
|
(self.int63() >> 31).az()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int31_n2(&mut self, n: u32) -> u32 {
|
|
|
|
|
let mut v = self.uint32();
|
|
|
|
|
let mut prod = u64::from(v) * u64::from(n);
|
|
|
|
@ -402,7 +661,7 @@ impl GoRand {
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
/// if n <= 0
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int_n(&mut self, n: usize) -> usize {
|
|
|
|
|
assert!((n > 0), "invalid argument to int_n");
|
|
|
|
|
if n < (1 << 31) {
|
|
|
|
@ -413,34 +672,65 @@ impl GoRand {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int(&mut self) -> usize {
|
|
|
|
|
self.int63().az()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shuffle pseudo-randomizes the order of elements. n is the number of elements. Shouffle
|
|
|
|
|
// panics if n < 0. swap swaps the elements with indices i and j.
|
|
|
|
|
#[flame]
|
|
|
|
|
fn shuffle(&mut self, n: usize, mut swap: impl FnMut(usize, usize)) {
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn shuffle<T>(&mut self, data: &mut [T]) {
|
|
|
|
|
// Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
|
|
|
|
// Shuffle really ought not be called with n that doesn't fit in 32 bits.
|
|
|
|
|
// Not only will it take a very long time, but with 2³¹! possible permutations,
|
|
|
|
|
// there's no way that any PRNG can have a big enough internal state to
|
|
|
|
|
// generate even a minuscule percentage of the possible permutations.
|
|
|
|
|
// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
|
|
|
|
|
let mut i = n.saturating_sub(1);
|
|
|
|
|
let mut i = data.len().saturating_sub(1);
|
|
|
|
|
while i > (1 << 31) - 1 - 1 {
|
|
|
|
|
let j = self.int63_n((i + 1) as u64).az();
|
|
|
|
|
swap(i, j);
|
|
|
|
|
data.swap(i, j);
|
|
|
|
|
i -= 1;
|
|
|
|
|
}
|
|
|
|
|
let mut i: u32 = i.az();
|
|
|
|
|
while i > 0 {
|
|
|
|
|
let j = self.int31_n2(i + 1);
|
|
|
|
|
swap(i.az(), j.az());
|
|
|
|
|
data.swap(i.az(), j.az());
|
|
|
|
|
i -= 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shuffle the array and check if it is deshuffled afterwards
|
|
|
|
|
fn shuffle_check(&mut self, data: &mut [Option<usize>]) -> bool {
|
|
|
|
|
let mut i = data.len().saturating_sub(1);
|
|
|
|
|
while i > (1 << 31) - 1 - 1 {
|
|
|
|
|
let j = self.int63_n((i + 1) as u64).az();
|
|
|
|
|
let datum = data[j];
|
|
|
|
|
if let Some(new_i) = datum {
|
|
|
|
|
if new_i != i {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
data.swap(i, j);
|
|
|
|
|
i -= 1;
|
|
|
|
|
}
|
|
|
|
|
let mut i: u32 = i.az();
|
|
|
|
|
while i > 0 {
|
|
|
|
|
let j = self.int31_n2(i + 1);
|
|
|
|
|
let i_usize = i.az();
|
|
|
|
|
let j_usize = j.az();
|
|
|
|
|
let datum = data[j_usize];
|
|
|
|
|
if let Some(i) = datum {
|
|
|
|
|
if i != i_usize {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
data.swap(i_usize, j_usize);
|
|
|
|
|
i -= 1;
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static CHAIN_RAW: LazyLock<Mmap> = LazyLock::new(|| unsafe {
|
|
|
|
@ -468,7 +758,7 @@ impl GoSource {
|
|
|
|
|
const MAX: u64 = 1 << 63;
|
|
|
|
|
const MASK: u64 = Self::MAX - 1;
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn new(seed: i64) -> Self {
|
|
|
|
|
let seed: i32 = (seed % i64::from(i32::MAX)).az();
|
|
|
|
|
let seed = match seed {
|
|
|
|
@ -490,13 +780,13 @@ impl GoSource {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GoSource {
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn int63(&mut self) -> u64 {
|
|
|
|
|
self.uint64() & Self::MASK
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn seed(&mut self, seed: i64) {
|
|
|
|
|
let seed: i32 = (seed % i64::from(i32::MAX)).az();
|
|
|
|
|
let seed = match seed {
|
|
|
|
@ -514,7 +804,7 @@ impl GoSource {
|
|
|
|
|
self.vec.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn uint64(&mut self) -> u64 {
|
|
|
|
|
self.tap = self.tap.checked_sub(1).unwrap_or(Self::LEN - 1);
|
|
|
|
|
self.feed = self.feed.checked_sub(1).unwrap_or(Self::LEN - 1);
|
|
|
|
@ -536,7 +826,7 @@ impl GoSource {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
fn seed_one(base: usize, i: u16) -> i64 {
|
|
|
|
|
let i = usize::from(i);
|
|
|
|
|
let base = base + 20 + i * 3;
|
|
|
|
@ -554,7 +844,7 @@ fn seed_one(base: usize, i: u16) -> i64 {
|
|
|
|
|
u ^ GoSource::COOKED[i]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[flame]
|
|
|
|
|
//#[flame]
|
|
|
|
|
const fn seedrand(x: i32) -> i32 {
|
|
|
|
|
const A: i32 = 48_271;
|
|
|
|
|
const Q: i32 = 44_488;
|
|
|
|
|