dootcamp #1
3 changed files with 111 additions and 47 deletions
|
@ -14,7 +14,7 @@ def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5)
|
||||||
else:
|
else:
|
||||||
root = random.choice(list(G.nodes))
|
root = random.choice(list(G.nodes))
|
||||||
|
|
||||||
def _hierarchy_pos(G, root, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None):
|
def _hierarchy_pos(G, root, width=2., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None):
|
||||||
if pos is None:
|
if pos is None:
|
||||||
pos = {root: (xcenter, vert_loc)}
|
pos = {root: (xcenter, vert_loc)}
|
||||||
else:
|
else:
|
||||||
|
@ -28,7 +28,7 @@ def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5)
|
||||||
nextx = xcenter - width / 2 - dx / 2
|
nextx = xcenter - width / 2 - dx / 2
|
||||||
for child in children:
|
for child in children:
|
||||||
nextx += dx
|
nextx += dx
|
||||||
pos = _hierarchy_pos(G, child, width=dx, vert_gap=vert_gap,
|
pos = _hierarchy_pos(G, child, width=dx*2.0, vert_gap=vert_gap,
|
||||||
vert_loc=vert_loc - vert_gap, xcenter=nextx,
|
vert_loc=vert_loc - vert_gap, xcenter=nextx,
|
||||||
pos=pos, parent=root)
|
pos=pos, parent=root)
|
||||||
return pos
|
return pos
|
||||||
|
|
|
@ -45,9 +45,9 @@ fn main() -> Result<()> {
|
||||||
let mut gemla = log_error(Gemla::<FighterNN>::new(
|
let mut gemla = log_error(Gemla::<FighterNN>::new(
|
||||||
&PathBuf::from(args.file),
|
&PathBuf::from(args.file),
|
||||||
GemlaConfig {
|
GemlaConfig {
|
||||||
generations_per_height: 10,
|
generations_per_height: 5,
|
||||||
overwrite: false,
|
overwrite: false,
|
||||||
shared_semaphore_concurrency_limit: 30,
|
shared_semaphore_concurrency_limit: 50,
|
||||||
},
|
},
|
||||||
DataFormat::Json,
|
DataFormat::Json,
|
||||||
))?;
|
))?;
|
||||||
|
@ -59,7 +59,7 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// Example placeholder loop to continuously run simulate
|
// Example placeholder loop to continuously run simulate
|
||||||
loop { // Arbitrary loop count for demonstration
|
loop { // Arbitrary loop count for demonstration
|
||||||
gemla.simulate(5).await?;
|
gemla.simulate(1).await?;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,18 @@ use async_trait::async_trait;
|
||||||
|
|
||||||
const BASE_DIR: &str = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations";
|
const BASE_DIR: &str = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations";
|
||||||
const POPULATION: usize = 50;
|
const POPULATION: usize = 50;
|
||||||
const NEURAL_NETWORK_SHAPE: &[u32; 5] = &[14, 20, 20, 12, 8];
|
|
||||||
|
const NEURAL_NETWORK_INPUTS: usize = 14;
|
||||||
|
const NEURAL_NETWORK_OUTPUTS: usize = 8;
|
||||||
|
const NEURAL_NETWORK_HIDDEN_LAYERS_MIN: usize = 1;
|
||||||
|
const NEURAL_NETWORK_HIDDEN_LAYERS_MAX: usize = 10;
|
||||||
|
const NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MIN: usize = 3;
|
||||||
|
const NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MAX: usize = 35;
|
||||||
|
const NEURAL_NETWORK_INITIAL_WEIGHT_MIN: f32 = -2.0;
|
||||||
|
const NEURAL_NETWORK_INITIAL_WEIGHT_MAX: f32 = 2.0;
|
||||||
|
const NEURAL_NETWORK_CROSSBREED_SEGMENTS_MIN: usize = 1;
|
||||||
|
const NEURAL_NETWORK_CROSSBREED_SEGMENTS_MAX: usize = 20;
|
||||||
|
|
||||||
const SIMULATION_ROUNDS: usize = 5;
|
const SIMULATION_ROUNDS: usize = 5;
|
||||||
const SURVIVAL_RATE: f32 = 0.5;
|
const SURVIVAL_RATE: f32 = 0.5;
|
||||||
const GAME_EXECUTABLE_PATH: &str = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Package\\Windows\\AI_Fight_Sim.exe";
|
const GAME_EXECUTABLE_PATH: &str = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Package\\Windows\\AI_Fight_Sim.exe";
|
||||||
|
@ -38,6 +49,9 @@ pub struct FighterNN {
|
||||||
pub generation: u64,
|
pub generation: u64,
|
||||||
// A map of each nn identifier in a generation and their physics score
|
// A map of each nn identifier in a generation and their physics score
|
||||||
pub scores: Vec<HashMap<u64, f32>>,
|
pub scores: Vec<HashMap<u64, f32>>,
|
||||||
|
// A map of the id of the nn in the current generation and their neural network shape
|
||||||
|
pub nn_shapes: HashMap<u64, Vec<u32>>,
|
||||||
|
pub crossbreed_segments: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -56,13 +70,25 @@ impl GeneticNode for FighterNN {
|
||||||
fs::create_dir_all(&gen_folder)
|
fs::create_dir_all(&gen_folder)
|
||||||
.with_context(|| format!("Failed to create or access the generation folder: {:?}", gen_folder))?;
|
.with_context(|| format!("Failed to create or access the generation folder: {:?}", gen_folder))?;
|
||||||
|
|
||||||
|
let nn_shapes = HashMap::new();
|
||||||
|
|
||||||
// Create the first generation in this folder
|
// Create the first generation in this folder
|
||||||
for i in 0..POPULATION {
|
for i in 0..POPULATION {
|
||||||
// Filenames are stored in the format of "xxxxxx_fighter_nn_0.net", "xxxxxx_fighter_nn_1.net", etc. Where xxxxxx is the folder name
|
// Filenames are stored in the format of "xxxxxx_fighter_nn_0.net", "xxxxxx_fighter_nn_1.net", etc. Where xxxxxx is the folder name
|
||||||
let nn = gen_folder.join(format!("{:06}_fighter_nn_{}.net", context.id, i));
|
let nn = gen_folder.join(format!("{:06}_fighter_nn_{}.net", context.id, i));
|
||||||
let mut fann = Fann::new(NEURAL_NETWORK_SHAPE)
|
|
||||||
|
// Randomly generate a neural network shape based on constants
|
||||||
|
let hidden_layers = thread_rng().gen_range(NEURAL_NETWORK_HIDDEN_LAYERS_MIN..NEURAL_NETWORK_HIDDEN_LAYERS_MAX);
|
||||||
|
let mut nn_shape = vec![NEURAL_NETWORK_INPUTS as u32];
|
||||||
|
for _ in 0..hidden_layers {
|
||||||
|
nn_shape.push(thread_rng().gen_range(NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MIN..NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MAX) as u32);
|
||||||
|
}
|
||||||
|
nn_shape.push(NEURAL_NETWORK_OUTPUTS as u32);
|
||||||
|
nn_shapes.insert(i as u64, nn_shape.clone());
|
||||||
|
|
||||||
|
let mut fann = Fann::new(nn_shape.as_slice())
|
||||||
.with_context(|| "Failed to create nn")?;
|
.with_context(|| "Failed to create nn")?;
|
||||||
fann.randomize_weights(-0.8, 0.8);
|
fann.randomize_weights(thread_rng().gen_range(NEURAL_NETWORK_INITIAL_WEIGHT_MIN..0.0), thread_rng().gen_range(0.0..=NEURAL_NETWORK_INITIAL_WEIGHT_MAX));
|
||||||
fann.set_activation_func_hidden(ActivationFunc::SigmoidSymmetric);
|
fann.set_activation_func_hidden(ActivationFunc::SigmoidSymmetric);
|
||||||
fann.set_activation_func_output(ActivationFunc::SigmoidSymmetric);
|
fann.set_activation_func_output(ActivationFunc::SigmoidSymmetric);
|
||||||
// This will overwrite any existing file with the same name
|
// This will overwrite any existing file with the same name
|
||||||
|
@ -76,6 +102,8 @@ impl GeneticNode for FighterNN {
|
||||||
population_size: POPULATION,
|
population_size: POPULATION,
|
||||||
generation: 0,
|
generation: 0,
|
||||||
scores: vec![HashMap::new()],
|
scores: vec![HashMap::new()],
|
||||||
|
nn_shapes,
|
||||||
|
crossbreed_segments: thread_rng().gen_range(NEURAL_NETWORK_CROSSBREED_SEGMENTS_MIN..NEURAL_NETWORK_CROSSBREED_SEGMENTS_MAX)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,20 +148,28 @@ impl GeneticNode for FighterNN {
|
||||||
|
|
||||||
// Check if score file already exists before running the simulation
|
// Check if score file already exists before running the simulation
|
||||||
if score_file.exists() {
|
if score_file.exists() {
|
||||||
let round_score = read_score_from_file(&score_file, &nn_id)
|
let round_score = read_score_from_file(&score_file, &nn_id).await
|
||||||
.with_context(|| format!("Failed to read score from file: {:?}", score_file_name))?;
|
.with_context(|| format!("Failed to read score from file: {:?}", score_file_name))?;
|
||||||
|
|
||||||
|
trace!("{} scored {}", nn_id, round_score);
|
||||||
|
|
||||||
return Ok::<f32, Error>(round_score);
|
return Ok::<f32, Error>(round_score);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the opposite round score has been determined
|
// Check if the opposite round score has been determined
|
||||||
let opposite_score_file = folder.join(format!("{}", generation)).join(format!("{}_vs_{}.txt", random_nn_id, nn_id));
|
let opposite_score_file = folder.join(format!("{}", generation)).join(format!("{}_vs_{}.txt", random_nn_id, nn_id));
|
||||||
if opposite_score_file.exists() {
|
if opposite_score_file.exists() {
|
||||||
let round_score = read_score_from_file(&opposite_score_file, &nn_id)
|
let round_score = read_score_from_file(&opposite_score_file, &nn_id).await
|
||||||
.with_context(|| format!("Failed to read score from file: {:?}", opposite_score_file))?;
|
.with_context(|| format!("Failed to read score from file: {:?}", opposite_score_file))?;
|
||||||
|
|
||||||
|
trace!("{} scored {}", nn_id, round_score);
|
||||||
|
|
||||||
return Ok::<f32, Error>(1.0 - round_score);
|
return Ok::<f32, Error>(1.0 - round_score);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _output = if thread_rng().gen_range(0..100) < 0 {
|
// Run simulation until score file is generated
|
||||||
|
while !score_file.exists() {
|
||||||
|
let _output = if thread_rng().gen_range(0..100) < 1 {
|
||||||
Command::new(GAME_EXECUTABLE_PATH)
|
Command::new(GAME_EXECUTABLE_PATH)
|
||||||
.arg(&config1_arg)
|
.arg(&config1_arg)
|
||||||
.arg(&config2_arg)
|
.arg(&config2_arg)
|
||||||
|
@ -149,14 +185,22 @@ impl GeneticNode for FighterNN {
|
||||||
.await
|
.await
|
||||||
.expect("Failed to execute game")
|
.expect("Failed to execute game")
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
drop(permit);
|
drop(permit);
|
||||||
|
|
||||||
// Read the score from the file
|
// Read the score from the file
|
||||||
let round_score = read_score_from_file(&score_file, &nn_id)
|
if score_file.exists() {
|
||||||
|
let round_score = read_score_from_file(&score_file, &nn_id).await
|
||||||
.with_context(|| format!("Failed to read score from file: {:?}", score_file_name))?;
|
.with_context(|| format!("Failed to read score from file: {:?}", score_file_name))?;
|
||||||
|
|
||||||
Ok::<f32, Error>(round_score)
|
trace!("{} scored {}", nn_id, round_score);
|
||||||
|
|
||||||
|
Ok(round_score)
|
||||||
|
} else {
|
||||||
|
trace!("Score file not found: {:?}", score_file_name);
|
||||||
|
Ok(0.0)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
simulations.push(future);
|
simulations.push(future);
|
||||||
|
@ -219,9 +263,11 @@ impl GeneticNode for FighterNN {
|
||||||
for i in 0..survivor_count {
|
for i in 0..survivor_count {
|
||||||
let nn_id = to_keep[i];
|
let nn_id = to_keep[i];
|
||||||
let nn = self.folder.join(format!("{}", self.generation)).join(format!("{:06}_fighter_nn_{}.net", self.id, nn_id));
|
let nn = self.folder.join(format!("{}", self.generation)).join(format!("{:06}_fighter_nn_{}.net", self.id, nn_id));
|
||||||
let mut fann = Fann::from_file(&nn)
|
let fann = Fann::from_file(&nn)
|
||||||
.with_context(|| format!("Failed to load nn"))?;
|
.with_context(|| format!("Failed to load nn"))?;
|
||||||
|
|
||||||
|
let new_fann = fann.try_clone();
|
||||||
|
|
||||||
// Load another nn from the current generation and cross breed it with the current nn
|
// Load another nn from the current generation and cross breed it with the current nn
|
||||||
let cross_nn = self.folder.join(format!("{}", self.generation)).join(format!("{:06}_fighter_nn_{}.net", self.id, to_keep[thread_rng().gen_range(0..survivor_count)]));
|
let cross_nn = self.folder.join(format!("{}", self.generation)).join(format!("{:06}_fighter_nn_{}.net", self.id, to_keep[thread_rng().gen_range(0..survivor_count)]));
|
||||||
let cross_fann = Fann::from_file(&cross_nn)
|
let cross_fann = Fann::from_file(&cross_nn)
|
||||||
|
@ -239,6 +285,7 @@ impl GeneticNode for FighterNN {
|
||||||
start_points.push(start_point);
|
start_points.push(start_point);
|
||||||
}
|
}
|
||||||
start_points.sort_unstable(); // Ensure segments are in order
|
start_points.sort_unstable(); // Ensure segments are in order
|
||||||
|
trace!("Crossbreeding Start points: {:?}", start_points);
|
||||||
|
|
||||||
for (j, &start) in start_points.iter().enumerate() {
|
for (j, &start) in start_points.iter().enumerate() {
|
||||||
let end = if j < segment_count - 1 {
|
let end = if j < segment_count - 1 {
|
||||||
|
@ -317,8 +364,12 @@ impl GeneticNode for FighterNN {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_score_from_file(file_path: &Path, nn_id: &str) -> Result<f32, io::Error> {
|
async fn read_score_from_file(file_path: &Path, nn_id: &str) -> Result<f32, io::Error> {
|
||||||
let file = File::open(file_path)?;
|
let mut attempts = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match File::open(file_path) {
|
||||||
|
Ok(file) => {
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
|
@ -331,8 +382,21 @@ fn read_score_from_file(file_path: &Path, nn_id: &str) -> Result<f32, io::Error>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::NotFound,
|
io::ErrorKind::NotFound,
|
||||||
"NN ID not found in scores file",
|
"NN ID not found in scores file",
|
||||||
))
|
));
|
||||||
|
},
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::PermissionDenied || e.kind() == io::ErrorKind::Other => {
|
||||||
|
if attempts >= 5 { // Attempt 5 times before giving up.
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts += 1;
|
||||||
|
// wait 1 second to ensure the file is written
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
|
},
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue