575 lines
15 KiB
Rust
575 lines
15 KiB
Rust
use rayon::prelude::*;
|
|
use std::ops::{Add, Index, IndexMut, Sub};
|
|
|
|
pub enum ReadError {
|
|
BadSymbol,
|
|
}
|
|
|
|
pub enum Error {
|
|
Read(ReadError),
|
|
BadPopSize,
|
|
BadMut,
|
|
BadCrossRatio,
|
|
BadMove(Pos),
|
|
}
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
#[derive(PartialEq, Copy, Clone)]
|
|
enum Direction {
|
|
U,
|
|
D,
|
|
L,
|
|
R,
|
|
}
|
|
|
|
#[derive(PartialEq, Copy, Clone)]
|
|
enum Cell {
|
|
Wall,
|
|
Box,
|
|
Goal(bool),
|
|
None,
|
|
}
|
|
|
|
#[derive(PartialEq, Clone)]
|
|
enum Solution {
|
|
Ok,
|
|
Win,
|
|
None,
|
|
}
|
|
|
|
#[derive(PartialEq, Default, Clone, Copy)]
|
|
pub struct Pos(isize, isize);
|
|
|
|
impl Add for Pos {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self {
|
|
Self(self.0 + other.0, self.1 + other.1)
|
|
}
|
|
}
|
|
|
|
impl Sub for Pos {
|
|
type Output = Self;
|
|
|
|
fn sub(self, other: Self) -> Self {
|
|
Self(self.0 - other.0, self.1 - other.1)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Pos {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{} {}", self.0, self.1)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for ReadError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
ReadError::BadSymbol => write!(f, "bad symbol"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Error {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Error::Read(e) => write!(f, "{e}"),
|
|
Error::BadPopSize => write!(f, "population size cannot be 0"),
|
|
Error::BadMut => write!(f, "mutation length too large"),
|
|
Error::BadCrossRatio => write!(f, "crossover ratio cannot be 0"),
|
|
Error::BadMove(pos) => write!(f, "invalid move ({pos})"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
struct Level {
|
|
size: Pos,
|
|
player: Pos,
|
|
map: Vec<Cell>,
|
|
}
|
|
|
|
impl Index<Pos> for Level {
|
|
type Output = Cell;
|
|
|
|
fn index(&self, pos: Pos) -> &Self::Output {
|
|
&self.map[((pos.1 * self.size.0) + pos.0) as usize]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<Pos> for Level {
|
|
fn index_mut(&mut self, pos: Pos) -> &mut Self::Output {
|
|
&mut self.map[((pos.1 * self.size.0) + pos.0) as usize]
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Level {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
for i in 0..self.size.1 {
|
|
for j in 0..self.size.0 {
|
|
if Pos(j, i) == self.player {
|
|
write!(f, "@")?;
|
|
} else {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match self[Pos(j, i)] {
|
|
Cell::Wall => 'X',
|
|
Cell::Box => '*',
|
|
Cell::Goal(true) => '!',
|
|
Cell::Goal(false) => '.',
|
|
Cell::None => ' ',
|
|
}
|
|
)?;
|
|
}
|
|
}
|
|
writeln!(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Level {
|
|
fn read<R: std::io::BufRead>(file: R) -> std::result::Result<Self, ReadError> {
|
|
let (mut player, mut size) = (Pos::default(), Pos::default());
|
|
|
|
let map = file
|
|
.lines()
|
|
.flat_map(|l| {
|
|
let l = l.unwrap();
|
|
|
|
if l.len() > size.0 as usize {
|
|
size.0 = l.len() as isize;
|
|
}
|
|
|
|
size.1 += 1;
|
|
l.char_indices()
|
|
.map(|(i, c)| match c {
|
|
'@' => {
|
|
player.0 = i as isize;
|
|
player.1 = size.1 - 1;
|
|
Ok(Cell::None)
|
|
}
|
|
'X' => Ok(Cell::Wall),
|
|
'*' => Ok(Cell::Box),
|
|
'g' => Ok(Cell::Goal(false)),
|
|
'.' => Ok(Cell::None),
|
|
_ => Err(ReadError::BadSymbol),
|
|
})
|
|
.collect::<Vec<std::result::Result<_, _>>>()
|
|
})
|
|
.collect::<std::result::Result<Vec<_>, _>>()?;
|
|
Ok(Self { size, player, map })
|
|
}
|
|
|
|
fn read_rle(map: String) -> std::result::Result<Self, ReadError> {
|
|
let mut n: usize = 1;
|
|
|
|
Self::read(
|
|
map.chars()
|
|
.filter_map(|c| match c {
|
|
'0'..='9' => {
|
|
n = c.to_digit(10).unwrap() as usize;
|
|
None
|
|
}
|
|
'|' => Some("\n".into()),
|
|
_ => Some(c.to_string().repeat(n)),
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.join("")
|
|
.as_bytes(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
struct State {
|
|
lvl: Level,
|
|
}
|
|
|
|
impl State {
|
|
fn new(lvl: Level) -> Self {
|
|
State { lvl }
|
|
}
|
|
|
|
fn toggle_box(&mut self, pos: &Pos) {
|
|
self.lvl[*pos] = if self.lvl[*pos] == Cell::Goal(true) {
|
|
Cell::Goal(false)
|
|
} else {
|
|
Cell::None
|
|
};
|
|
}
|
|
|
|
fn move_pos(&mut self, pos: Pos) -> Result<(bool, bool)> {
|
|
let dpos = self.lvl.player + pos;
|
|
|
|
if ((dpos.1 * self.lvl.size.0) + dpos.0) as usize >= self.lvl.map.len() {
|
|
return Err(Error::BadMove(dpos));
|
|
}
|
|
|
|
match self.lvl[dpos] {
|
|
Cell::None | Cell::Goal(false) => self.lvl.player = dpos,
|
|
Cell::Box | Cell::Goal(true) => {
|
|
let bpos = dpos + pos;
|
|
|
|
if ((bpos.1 * self.lvl.size.0) + bpos.0) as usize >= self.lvl.map.len() {
|
|
return Err(Error::BadMove(bpos));
|
|
}
|
|
|
|
match self.lvl[bpos] {
|
|
Cell::None => {
|
|
self.toggle_box(&dpos);
|
|
self.lvl[bpos] = Cell::Box;
|
|
self.lvl.player = dpos;
|
|
return Ok((false, false));
|
|
}
|
|
Cell::Goal(false) => {
|
|
self.toggle_box(&dpos);
|
|
self.lvl[bpos] = Cell::Goal(true);
|
|
self.lvl.player = dpos;
|
|
return Ok((true, false));
|
|
}
|
|
_ => return Ok((false, true)),
|
|
}
|
|
}
|
|
_ => return Ok((false, true)),
|
|
}
|
|
|
|
Ok((false, false))
|
|
}
|
|
|
|
fn move_dir(&mut self, dir: Direction) -> Result<(bool, bool)> {
|
|
match dir {
|
|
Direction::U => self.move_pos(Pos(0, -1)),
|
|
Direction::D => self.move_pos(Pos(0, 1)),
|
|
Direction::L => self.move_pos(Pos(-1, 0)),
|
|
Direction::R => self.move_pos(Pos(1, 0)),
|
|
}
|
|
}
|
|
|
|
fn test_box(&self, pos: Pos, bt: bool) -> bool {
|
|
let dir: &[Pos] = &[Pos(0, -1), Pos(0, 1), Pos(-1, 0), Pos(1, 0)];
|
|
let mut mbc = 0;
|
|
|
|
for i in dir {
|
|
match self.lvl[pos + *i] {
|
|
Cell::Goal(false) | Cell::None => {
|
|
let dpos = pos - *i;
|
|
|
|
match self.lvl[dpos] {
|
|
Cell::Goal(false) | Cell::None => mbc += 1,
|
|
Cell::Box => {
|
|
if bt {
|
|
mbc += self.test_box(dpos, false) as usize
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
mbc != 0
|
|
}
|
|
|
|
fn dist_box(&self, pos: Pos) -> usize {
|
|
let mut d = 0;
|
|
|
|
for i in 0..self.lvl.size.1 {
|
|
for j in 0..self.lvl.size.0 {
|
|
if self.lvl[Pos(j, i)] == Cell::Goal(false) {
|
|
d += ((j as i32 - pos.0 as i32).abs() + (i as i32 - pos.1 as i32).abs())
|
|
as usize;
|
|
}
|
|
}
|
|
}
|
|
|
|
d
|
|
}
|
|
|
|
fn dist_boxes(&self) -> usize {
|
|
let (mut max, mut rb) = (0, 0);
|
|
|
|
for i in 0..self.lvl.size.1 {
|
|
for j in 0..self.lvl.size.0 {
|
|
if self.lvl[Pos(j, i)] == Cell::Box {
|
|
max += self.dist_box(Pos(j, i));
|
|
rb += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
max * rb
|
|
}
|
|
|
|
fn evaluate(&self) -> Solution {
|
|
let mut cond = Solution::Win;
|
|
|
|
for i in 0..self.lvl.size.1 {
|
|
for j in 0..self.lvl.size.0 {
|
|
match self.lvl[Pos(j, i)] {
|
|
Cell::Goal(false) => cond = Solution::Ok,
|
|
Cell::Box => {
|
|
if !self.test_box(Pos(j, i), true) {
|
|
return Solution::None;
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
cond
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Agent {
|
|
state: State,
|
|
dna: Vec<Direction>,
|
|
score: f32,
|
|
dir: usize,
|
|
goal: Solution,
|
|
}
|
|
|
|
impl Agent {
|
|
const DIR_TABLE: &'static [Direction] =
|
|
&[Direction::U, Direction::D, Direction::L, Direction::R];
|
|
|
|
fn chromo() -> Direction {
|
|
Self::DIR_TABLE[rand::random::<usize>() % Self::DIR_TABLE.len()]
|
|
}
|
|
|
|
fn with_dna(state: State, dna: Vec<Direction>) -> Self {
|
|
Self {
|
|
state,
|
|
dna,
|
|
score: 0.0,
|
|
dir: 0,
|
|
goal: Solution::Ok,
|
|
}
|
|
}
|
|
|
|
fn new(state: State, size: usize) -> Self {
|
|
let dna: Vec<Direction> = (0..size).map(|_| Self::chromo()).collect();
|
|
|
|
Self::with_dna(state, dna)
|
|
}
|
|
|
|
fn cross(&mut self, seed: &Self, cr: usize) {
|
|
let ratio = (rand::random::<usize>() % cr) + 1;
|
|
let len = self.dna.len();
|
|
|
|
self.dna[..len / ratio].copy_from_slice(&seed.dna[..len / ratio]);
|
|
}
|
|
|
|
fn mutate(&mut self, max: usize) {
|
|
let len = self.dna.len();
|
|
let ni = rand::random::<usize>() % max;
|
|
let nd = rand::random::<usize>() % len;
|
|
|
|
for x in 0..ni {
|
|
self.dna[(nd + x) % len] = Agent::chromo();
|
|
}
|
|
}
|
|
|
|
fn move_dna(&mut self) -> Result<Solution> {
|
|
if self.dir == self.dna.len() || self.goal == Solution::None {
|
|
Ok(Solution::None)
|
|
} else {
|
|
let (mb, hit) = self.state.move_dir(self.dna[self.dir])?;
|
|
|
|
if mb {
|
|
self.score += 0.00021;
|
|
}
|
|
|
|
if hit {
|
|
self.score -= 0.00080;
|
|
}
|
|
|
|
self.dir += 1;
|
|
Ok(self.state.evaluate())
|
|
}
|
|
}
|
|
|
|
fn evaluate(&mut self) -> Result<()> {
|
|
loop {
|
|
let player = self.state.lvl.player;
|
|
|
|
match self.move_dna()? {
|
|
Solution::Ok => (),
|
|
Solution::Win => {
|
|
self.score += 100000.0;
|
|
self.goal = Solution::Win;
|
|
break;
|
|
}
|
|
Solution::None => {
|
|
self.score -= 0.0032;
|
|
self.goal = Solution::None;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if player == self.state.lvl.player {
|
|
self.score -= 0.0010;
|
|
}
|
|
}
|
|
|
|
self.score -= 1024.0 + self.state.dist_boxes() as f32;
|
|
Ok(())
|
|
}
|
|
|
|
fn solution(&self) -> String {
|
|
(0..self.dir)
|
|
.map(|i| match self.dna[i] {
|
|
Direction::U => "U",
|
|
Direction::D => "D",
|
|
Direction::L => "L",
|
|
Direction::R => "R",
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
struct Population {
|
|
gen: usize,
|
|
epop_max: usize,
|
|
mut_max: usize,
|
|
cross_ratio: usize,
|
|
map: Level,
|
|
life: Vec<Agent>,
|
|
}
|
|
|
|
pub struct Config {
|
|
pub num_threads: usize,
|
|
pub dna_size: usize,
|
|
pub pop_size: usize,
|
|
pub epop_max: usize,
|
|
pub mut_max: usize,
|
|
pub gen_max: usize,
|
|
pub gen_depth: usize,
|
|
pub cross_ratio: usize,
|
|
pub map_output: bool,
|
|
}
|
|
|
|
impl Population {
|
|
fn new(config: &Config, map: Level) -> Self {
|
|
let life: Vec<Agent> = (0..config.pop_size)
|
|
.map(|_| Agent::new(State::new(map.clone()), config.dna_size))
|
|
.collect();
|
|
|
|
Self {
|
|
gen: 0,
|
|
epop_max: config.epop_max,
|
|
mut_max: config.mut_max,
|
|
cross_ratio: config.cross_ratio,
|
|
map,
|
|
life,
|
|
}
|
|
}
|
|
|
|
fn step(&mut self) -> Result<()> {
|
|
self.life
|
|
.par_iter_mut()
|
|
.map(|a| a.evaluate())
|
|
.collect::<Result<Vec<()>>>()?;
|
|
self.life.par_sort_by(|a, b| b.score.total_cmp(&a.score));
|
|
|
|
if self.life[0].goal != Solution::Win {
|
|
let epop: Vec<Agent> = (0..self.epop_max)
|
|
.map(|i| {
|
|
let mut a = self.life[i].clone();
|
|
|
|
a.cross(
|
|
if i % 2 == 0 {
|
|
&self.life[(i + 1) % self.epop_max]
|
|
} else {
|
|
&self.life[(i - 1) % self.epop_max]
|
|
},
|
|
self.cross_ratio,
|
|
);
|
|
a
|
|
})
|
|
.collect();
|
|
|
|
for i in epop.len()..self.life.len() {
|
|
let mut next = Agent::with_dna(
|
|
State::new(self.map.clone()),
|
|
if i % 2 == 0 {
|
|
epop[i % self.epop_max].dna.clone()
|
|
} else {
|
|
epop[(i + 1) % self.epop_max].dna.clone()
|
|
},
|
|
);
|
|
|
|
next.mutate(self.mut_max);
|
|
self.life[i] = next;
|
|
}
|
|
}
|
|
|
|
self.gen += 1;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn run(config: Config, rle: Option<String>) -> Result<()> {
|
|
rayon::ThreadPoolBuilder::new()
|
|
.num_threads(config.num_threads)
|
|
.build_global()
|
|
.unwrap();
|
|
|
|
if config.pop_size == 0 {
|
|
return Err(Error::BadPopSize);
|
|
}
|
|
|
|
if config.mut_max > config.dna_size {
|
|
return Err(Error::BadMut);
|
|
}
|
|
|
|
if config.cross_ratio == 0 {
|
|
return Err(Error::BadCrossRatio);
|
|
}
|
|
|
|
let map = if let Some(rle) = rle {
|
|
Level::read_rle(rle)
|
|
} else {
|
|
Level::read(std::io::stdin().lock())
|
|
}
|
|
.map_err(Error::Read)?;
|
|
|
|
let mut agents: Vec<Agent> = Vec::new();
|
|
|
|
for _ in 0..config.gen_depth {
|
|
let mut pop = Population::new(&config, map.clone());
|
|
|
|
if let Some(agent) = || -> Result<Option<Agent>> {
|
|
while pop.gen < config.gen_max {
|
|
pop.step()?;
|
|
|
|
if config.map_output {
|
|
print!("{}", pop.life[0].state.lvl);
|
|
}
|
|
|
|
println!("gen {} score {}", pop.gen, pop.life[0].score);
|
|
if pop.life[0].goal == Solution::Win {
|
|
return Ok(Some(pop.life[0].clone()));
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}()? {
|
|
agents.push(agent);
|
|
}
|
|
}
|
|
|
|
agents.par_sort_by_key(|a| a.dir);
|
|
|
|
if !agents.is_empty() {
|
|
println!("{}", agents[0].solution());
|
|
}
|
|
|
|
Ok(())
|
|
}
|