// https://codemyroad.wordpress.com/2013/04/14/tetris-ai-the-near-perfect-player/ use super::{Actor, State}; use crate::{ game::Action, playfield::{PLAYFIELD_HEIGHT, PLAYFIELD_WIDTH}, }; use rand::rngs::SmallRng; use rand::Rng; pub struct Parameters { total_height: f64, bumpiness: f64, holes: f64, complete_lines: f64, } impl Default for Parameters { fn default() -> Self { Self { total_height: 1.0, bumpiness: 1.0, holes: 1.0, complete_lines: 1.0, } } } impl Parameters { fn mutate(mut self, rng: &mut SmallRng) { let mutation_amt = rng.gen_range(-0.2, 0.2); match rng.gen_range(0, 4) { 0 => self.total_height += mutation_amt, 1 => self.bumpiness += mutation_amt, 2 => self.holes += mutation_amt, 3 => self.complete_lines += mutation_amt, _ => unreachable!(), } let normalization_factor = (self.total_height.powi(2) + self.bumpiness.powi(2) + self.holes.powi(2) + self.complete_lines.powi(2)) .sqrt(); self.total_height /= normalization_factor; self.bumpiness /= normalization_factor; self.holes /= normalization_factor; self.complete_lines /= normalization_factor; } fn dot_multiply(&self, other: &Self) -> f64 { self.total_height * other.total_height + self.bumpiness * other.bumpiness + self.holes * other.holes + self.complete_lines * other.complete_lines } } pub struct GeneticHeuristicAgent { params: Parameters, } impl Default for GeneticHeuristicAgent { fn default() -> Self { Self { params: Parameters::default(), } } } impl GeneticHeuristicAgent { fn extract_features_from_state(state: &State) -> Parameters { let mut heights = [None; PLAYFIELD_WIDTH]; for r in 0..PLAYFIELD_HEIGHT { for c in 0..PLAYFIELD_WIDTH { if heights[c].is_none() && state.matrix[r][c].is_some() { heights[c] = Some(PLAYFIELD_HEIGHT - r); } } } let total_height = heights .iter() .map(|o| o.unwrap_or_else(|| 0)) .sum::() as f64; let bumpiness = heights .iter() .map(|o| o.unwrap_or_else(|| 0) as isize) .fold((0, 0), |(acc, prev), cur| (acc + (prev - cur).abs(), cur)) .0 as f64; let complete_lines = state .matrix .iter() .map(|row| row.iter().all(Option::is_some)) .map(|c| if c { 1.0 } else { 0.0 }) .sum::(); let mut holes = 0; for r in 1..PLAYFIELD_HEIGHT { for c in 0..PLAYFIELD_WIDTH { if state.matrix[r][c].is_none() && state.matrix[r - 1][c].is_some() { holes += 1; } } } Parameters { total_height, bumpiness, complete_lines, holes: holes as f64, } } fn get_heuristic(&self, state: &State, action: &Action) -> f64 { todo!(); } } impl Actor for GeneticHeuristicAgent { fn get_action(&self, rng: &mut SmallRng, state: &State, legal_actions: &[Action]) -> Action { *legal_actions .iter() .map(|action| (action, self.get_heuristic(state, action))) .max_by_key(|(action, heuristic)| (heuristic * 1_000_00.0) as usize) .unwrap() .0 } fn update( &mut self, state: State, action: Action, next_state: State, next_legal_actions: &[Action], reward: f64, ) { unimplemented!() } fn set_learning_rate(&mut self, learning_rate: f64) { unimplemented!() } fn set_exploration_prob(&mut self, exploration_prob: f64) { unimplemented!() } fn set_discount_rate(&mut self, discount_rate: f64) { unimplemented!() } fn dbg(&self) { unimplemented!() } }