|
|
|
use std::collections::HashSet;
|
|
|
|
use rand::prelude::{SliceRandom, StdRng};
|
|
|
|
use crate::city::{Rectangle, HOUSE_RANGE, SIZE, House, HouseLayout};
|
|
|
|
use itertools::iproduct;
|
|
|
|
|
|
|
|
pub enum RectangleSearchError {
|
|
|
|
Useless,
|
|
|
|
Unsatisfiable
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<House>) -> Result<Rectangle, RectangleSearchError> {
|
|
|
|
// This is a generalization of get_valid_move_rectangle, it's basically the same thing,
|
|
|
|
// just with a dynamic rectangles_containing_count
|
|
|
|
|
|
|
|
// We first establish a bounding box for an that has to be covered if all houses are removed.
|
|
|
|
let mut covered_rect: Option<Rectangle> = None;
|
|
|
|
for house in houses {
|
|
|
|
let range_rect = house.range_rectangle();
|
|
|
|
for y in range_rect.top..=range_rect.bottom {
|
|
|
|
for x in range_rect.left..=range_rect.right {
|
|
|
|
// We count how many rectangles of houses contain this xy position.
|
|
|
|
let mut rectangles_containing_count = 0;
|
|
|
|
for house in houses {
|
|
|
|
let rect = house.range_rectangle();
|
|
|
|
if rect.is_inside(x, y) {
|
|
|
|
rectangles_containing_count += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this house is covered by the exact amount of rectangles,
|
|
|
|
// then removing all input houses would uncover this position.
|
|
|
|
// It cannot be less than the rectangle count, and more means there
|
|
|
|
// is another house covering it as well.
|
|
|
|
if layout.cover_count_xy(x, y) == rectangles_containing_count && layout.city.is_house_xy(x, y) {
|
|
|
|
if let Some(cover) = &mut covered_rect {
|
|
|
|
cover.left = cover.left.min(x);
|
|
|
|
cover.right = cover.right.max(x);
|
|
|
|
cover.top = cover.top.min(y);
|
|
|
|
cover.bottom = cover.bottom.max(y);
|
|
|
|
} else {
|
|
|
|
covered_rect = Some(Rectangle { left: x, right: x, top: y, bottom: y });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if covered_rect.is_none() {
|
|
|
|
// Unnecessary set of houses.
|
|
|
|
return Err(RectangleSearchError::Useless);
|
|
|
|
}
|
|
|
|
|
|
|
|
let covered_rect = covered_rect.unwrap();
|
|
|
|
|
|
|
|
let height_margin = HOUSE_RANGE as i32 - covered_rect.height() as i32;
|
|
|
|
let width_margin = HOUSE_RANGE as i32 - covered_rect.width() as i32;
|
|
|
|
|
|
|
|
let top = (covered_rect.top as i32 - height_margin).max(0) as usize;
|
|
|
|
let left = (covered_rect.left as i32 - width_margin).max(0) as usize;
|
|
|
|
let bottom = (covered_rect.bottom + height_margin as usize).min(SIZE - 1);
|
|
|
|
let right = (covered_rect.right + width_margin as usize).min(SIZE - 1);
|
|
|
|
|
|
|
|
if top > bottom || left > right {
|
|
|
|
// Unsatisfiable rectangle by one house
|
|
|
|
return Err(RectangleSearchError::Unsatisfiable);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Rectangle { left, right, top, bottom })
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectangle, RectangleSearchError> {
|
|
|
|
// We first establish a bounding box for an that has to be covered if the house is removed.
|
|
|
|
let mut covered_rect: Option<Rectangle> = None;
|
|
|
|
|
|
|
|
let range_rect = house.range_rectangle();
|
|
|
|
for y in range_rect.top..=range_rect.bottom {
|
|
|
|
for x in range_rect.left..=range_rect.right {
|
|
|
|
if layout.cover_count_xy(x, y) == 1 && layout.city.is_house_xy(x, y) {
|
|
|
|
// This house is only covered by the house, it has to be covered from the new position as well.
|
|
|
|
if let Some(cover) = &mut covered_rect {
|
|
|
|
cover.left = cover.left.min(x);
|
|
|
|
cover.right = cover.right.max(x);
|
|
|
|
cover.top = cover.top.min(y);
|
|
|
|
cover.bottom = cover.bottom.max(y);
|
|
|
|
} else {
|
|
|
|
covered_rect = Some(Rectangle { left: x, right: x, top: y, bottom: y });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if covered_rect.is_none() {
|
|
|
|
return Err(RectangleSearchError::Useless)
|
|
|
|
}
|
|
|
|
|
|
|
|
let covered_rect = covered_rect.unwrap();
|
|
|
|
|
|
|
|
// The distance of the rectangle from the original box tells us how much the house can move.
|
|
|
|
let dist_left = covered_rect.left - range_rect.left;
|
|
|
|
let dist_right = range_rect.right - covered_rect.right;
|
|
|
|
let dist_top = covered_rect.top - range_rect.top;
|
|
|
|
let dist_bottom = range_rect.bottom - covered_rect.bottom;
|
|
|
|
|
|
|
|
let left = if house.x <= dist_right { 0 } else { house.x - dist_right };
|
|
|
|
let right = if house.x >= SIZE - 1 - dist_left { SIZE - 1 } else { house.x + dist_left };
|
|
|
|
let top = if house.y <= dist_bottom { 0 } else { house.y - dist_bottom };
|
|
|
|
let bottom = if house.y >= SIZE - 1 - dist_top { SIZE - 1 } else { house.y + dist_top };
|
|
|
|
|
|
|
|
let valid_move_rectangle = Rectangle {
|
|
|
|
left, right, top, bottom
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(valid_move_rectangle)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut StdRng) -> bool {
|
|
|
|
let mut improved = false;
|
|
|
|
let mut untried_houses = layout.houses().clone();
|
|
|
|
untried_houses.shuffle(&mut rng);
|
|
|
|
|
|
|
|
while untried_houses.len() > 0 {
|
|
|
|
let house = untried_houses.pop().unwrap();
|
|
|
|
let house_index = layout.houses().iter().position(|x| *x == house).unwrap();
|
|
|
|
|
|
|
|
let move_rectangle = match get_valid_move_rectangle(&layout, house) {
|
|
|
|
Ok(move_rectangle) => move_rectangle,
|
|
|
|
Err(RectangleSearchError::Useless) => {
|
|
|
|
//let old_price = layout.price();
|
|
|
|
layout.remove_house(house_index);
|
|
|
|
//let new_price = layout.price();
|
|
|
|
//let price_diff = new_price as i64 - old_price as i64;
|
|
|
|
//eprintln!(" candidate is valid, price diff: {}.", price_diff);
|
|
|
|
//eprintln!("Removed a house (useless), diff {}", price_diff);
|
|
|
|
//eprintln!("Improved price: {}", new_price);
|
|
|
|
improved = true;
|
|
|
|
untried_houses = layout.houses().clone();
|
|
|
|
untried_houses.shuffle(&mut rng);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
_ => unreachable!()
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: Not needed, can just store best
|
|
|
|
let mut new_candidates = Vec::new();
|
|
|
|
for new_y in move_rectangle.top..=move_rectangle.bottom {
|
|
|
|
for new_x in move_rectangle.left..=move_rectangle.right {
|
|
|
|
if layout.city.is_house_xy(new_x, new_y) && layout.city.get_price_xy(new_x, new_y) < layout.city.get_price(house) {
|
|
|
|
new_candidates.push(House::new(new_x, new_y));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
new_candidates.sort_by(|a, b| layout.city.get_price(*a).cmp(&layout.city.get_price(*b)));
|
|
|
|
if new_candidates.len() == 0 {
|
|
|
|
//eprintln!("Did not find candidate");
|
|
|
|
} else {
|
|
|
|
for (i, &candidate) in new_candidates.iter().enumerate() {
|
|
|
|
//eprint!("Found candidate {}...", i);
|
|
|
|
|
|
|
|
//let old_price = layout.price();
|
|
|
|
layout.remove_house(house_index);
|
|
|
|
layout.add_house(candidate);
|
|
|
|
|
|
|
|
assert!(layout.is_valid());
|
|
|
|
//let new_price = layout.price();
|
|
|
|
//let price_diff = new_price as i64 - old_price as i64;
|
|
|
|
//eprintln!(" candidate is valid, price diff: {}.", price_diff);
|
|
|
|
//eprintln!("Improved price: {}", new_price);
|
|
|
|
improved = true;
|
|
|
|
untried_houses = layout.houses().clone();
|
|
|
|
untried_houses.shuffle(&mut rng);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
improved
|
|
|
|
}
|
|
|
|
pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {
|
|
|
|
let mut improved = false;
|
|
|
|
|
|
|
|
loop {
|
|
|
|
// This here is a hack for being unable to modify the houses while looping through them.
|
|
|
|
// We instead go through the houses repeatedly and remember which pairs we have already
|
|
|
|
// tried by hashing their values because they can and do move throughout the layout Vec
|
|
|
|
// as it's being modified.
|
|
|
|
let mut checked = HashSet::new();
|
|
|
|
|
|
|
|
let mut loop_improved = false;
|
|
|
|
loop {
|
|
|
|
let mut merge = None;
|
|
|
|
|
|
|
|
'outer_houses: for i in 0..layout.houses().len() {
|
|
|
|
for j in i + 1..layout.houses().len() {
|
|
|
|
let house1 = layout.houses()[i];
|
|
|
|
let house2 = layout.houses()[j];
|
|
|
|
|
|
|
|
let x_dist = (house1.x as i32 - house2.x as i32).abs() as usize;
|
|
|
|
let y_dist = (house1.y as i32 - house2.y as i32).abs() as usize;
|
|
|
|
if x_dist > 4 * HOUSE_RANGE || y_dist > 4 * HOUSE_RANGE {
|
|
|
|
// Never close enough to merge
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if checked.contains(&(house1, house2)) || checked.contains(&(house2, house1)) {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
checked.insert((house1, house2));
|
|
|
|
}
|
|
|
|
|
|
|
|
match get_valid_move_rectangle_multiple(&layout, &vec! {house1, house2}) {
|
|
|
|
Ok(rect) => {
|
|
|
|
let mut cheapest = None;
|
|
|
|
for y in rect.top..=rect.bottom {
|
|
|
|
for x in rect.left..=rect.right {
|
|
|
|
if !layout.city.is_house_xy(x, y) { continue; }
|
|
|
|
let price = layout.city.get_price_xy(x, y);
|
|
|
|
match cheapest {
|
|
|
|
None => cheapest = Some((x, y, price)),
|
|
|
|
Some((_, _, cheapest_price)) if price < cheapest_price => cheapest = Some((x, y, price)),
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some((x, y, price)) = cheapest {
|
|
|
|
if price >= layout.city.get_price(house1) + layout.city.get_price(house2) {
|
|
|
|
// Merging not worth
|
|
|
|
//eprintln!("Merging not worth!");
|
|
|
|
} else {
|
|
|
|
merge = Some((i, j, House::new(x, y)));
|
|
|
|
break 'outer_houses;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(RectangleSearchError::Useless) => eprintln!("Found useless pair of houses, not solving!"),
|
|
|
|
Err(RectangleSearchError::Unsatisfiable) => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some((i, j, house)) = merge {
|
|
|
|
let old_price = layout.price();
|
|
|
|
assert!(i < j);
|
|
|
|
layout.remove_house(j);
|
|
|
|
layout.remove_house(i);
|
|
|
|
layout.add_house(house);
|
|
|
|
|
|
|
|
assert!(layout.is_valid());
|
|
|
|
let new_price = layout.price();
|
|
|
|
let price_diff = new_price as i32 - old_price as i32;
|
|
|
|
eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff);
|
|
|
|
improved = true;
|
|
|
|
loop_improved = true;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !loop_improved {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
improved
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn improve_move_houses_pairwise(layout: &mut HouseLayout) -> bool {
|
|
|
|
// TODO: Implement layout move house to avoid changing indices
|
|
|
|
for i in 0..layout.houses().len() {
|
|
|
|
for j in i + 1..layout.houses().len() {
|
|
|
|
let house1 = layout.houses()[i];
|
|
|
|
let house2 = layout.houses()[j];
|
|
|
|
let x_dist = (house1.x as i32 - house2.x as i32).abs() as usize;
|
|
|
|
let y_dist = (house1.y as i32 - house2.y as i32).abs() as usize;
|
|
|
|
if x_dist > 2 * HOUSE_RANGE + 1 || y_dist > 2 * HOUSE_RANGE + 1 {
|
|
|
|
// Not close enough to overlap or touch areas (can move on its own)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(distances) = get_dual_move_distances(&layout, i, j) {
|
|
|
|
eprintln!("Houses {} {} move distances: L{} R{} T{} B{}", i, j, distances.left, distances.right, distances.up, distances.down);
|
|
|
|
let mut best_move = None;
|
|
|
|
|
|
|
|
let old_price = layout.city.get_price(house1) + layout.city.get_price(house2);
|
|
|
|
|
|
|
|
let left_deltas = (1..distances.left).map(|x| (-(x as i32), 0));
|
|
|
|
let right_deltas = (1..distances.right).map(|x| (x as i32, 0));
|
|
|
|
let up_deltas = (1..distances.up).map(|x| (0, -(x as i32)));
|
|
|
|
let down_deltas = (1..distances.down).map(|x| (0, x as i32));
|
|
|
|
let move_deltas = left_deltas.chain(right_deltas).chain(up_deltas).chain(down_deltas);
|
|
|
|
|
|
|
|
for (x_delta, y_delta) in move_deltas {
|
|
|
|
let new_house1 = House::new(
|
|
|
|
(house1.x as i32 + x_delta) as usize,
|
|
|
|
(house1.y as i32 + y_delta) as usize,
|
|
|
|
);
|
|
|
|
let new_house2 = House::new(
|
|
|
|
(house2.x as i32 + x_delta) as usize,
|
|
|
|
(house2.y as i32 + y_delta) as usize,
|
|
|
|
);
|
|
|
|
|
|
|
|
if !layout.city.is_house(new_house1) || !layout.city.is_house(new_house2) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let new_price = layout.city.get_price(new_house1) + layout.city.get_price(new_house2);
|
|
|
|
eprintln!("Move x{} y{}, diff {} (price {}->{})", x_delta, y_delta, new_price as i32 - old_price as i32, old_price, new_price);
|
|
|
|
|
|
|
|
if new_price < old_price {
|
|
|
|
match best_move {
|
|
|
|
None => best_move = Some((x_delta, y_delta, new_price)),
|
|
|
|
Some((_, _, best_price)) if new_price < best_price => {
|
|
|
|
best_move = Some((x_delta, y_delta, new_price));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some((x_delta, y_delta, best_price)) = best_move {
|
|
|
|
eprintln!("Best move x{} y{}, diff {} (price {}->{})", x_delta, y_delta, best_price as i32 - old_price as i32, old_price, best_price);
|
|
|
|
// TODO: IMPLEMENT MOVE
|
|
|
|
} else {
|
|
|
|
eprintln!("No move worth it.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unimplemented!()
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MoveDistances {
|
|
|
|
left: usize,
|
|
|
|
right: usize,
|
|
|
|
up: usize,
|
|
|
|
down: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_dual_move_distances(layout: &HouseLayout, house1_index: usize, house2_index: usize) -> Option<MoveDistances> {
|
|
|
|
let house1 = layout.houses()[house1_index];
|
|
|
|
let house2 = layout.houses()[house2_index];
|
|
|
|
|
|
|
|
let rect1 = house1.range_rectangle();
|
|
|
|
let rect2 = house2.range_rectangle();
|
|
|
|
|
|
|
|
let top1 = rect1.top.min(rect2.top);
|
|
|
|
let top2 = rect1.top.max(rect2.top);
|
|
|
|
let bottom1 = rect1.bottom.min(rect2.bottom);
|
|
|
|
let bottom2 = rect1.bottom.max(rect2.bottom);
|
|
|
|
let left1 = rect1.left.min(rect2.left);
|
|
|
|
let left2 = rect1.left.max(rect2.left);
|
|
|
|
let right1 = rect1.right.min(rect2.right);
|
|
|
|
let right2 = rect1.right.max(rect2.right);
|
|
|
|
|
|
|
|
let left_rect = if house1.x <= house2.x { rect1 } else { rect2 };
|
|
|
|
let right_rect = if house1.x <= house2.x { rect2 } else { rect1 };
|
|
|
|
let top_rect = if house1.y <= house2.y { rect1 } else { rect2 };
|
|
|
|
let bottom_rect = if house1.y <= house2.y { rect2 } else { rect1 };
|
|
|
|
|
|
|
|
let margin_left = left1..left2;
|
|
|
|
let shared_x = left2..=right1;
|
|
|
|
let margin_right = right1 + 1..=right2;
|
|
|
|
let margin_top = top1..top2;
|
|
|
|
let shared_y = top2..=bottom1;
|
|
|
|
let margin_bottom = bottom1 + 1..=bottom2;
|
|
|
|
|
|
|
|
let mut top_distance = SIZE;
|
|
|
|
let mut bottom_distance = SIZE;
|
|
|
|
let mut right_distance = SIZE;
|
|
|
|
let mut left_distance = SIZE;
|
|
|
|
|
|
|
|
// We check the same tile twice if it's in both rectangles (and both shared_x and shared_y),
|
|
|
|
// this could be made more efficient by dividing the rectangles into 5 zones.
|
|
|
|
let rect1 = iproduct!(rect1.top..=rect1.bottom, rect1.left..=rect1.right);
|
|
|
|
let rect2 = iproduct!(rect2.top..=rect2.bottom, rect2.left..=rect2.right);
|
|
|
|
for (y, x) in rect1.chain(rect2) {
|
|
|
|
let shared_both = shared_x.contains(&x) && shared_y.contains(&y);
|
|
|
|
let min_cover_count = if shared_both { 2 } else { 1 };
|
|
|
|
if !layout.city.is_house_xy(x, y) && layout.cover_count_xy(x, y) != min_cover_count {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if margin_left.contains(&x) {
|
|
|
|
top_distance = top_distance.min(y - left_rect.top);
|
|
|
|
bottom_distance = bottom_distance.min(left_rect.bottom - y);
|
|
|
|
} else if shared_x.contains(&x) {
|
|
|
|
top_distance = top_distance.min(y - left_rect.top.min(right_rect.top));
|
|
|
|
bottom_distance = bottom_distance.min(left_rect.bottom.max(right_rect.bottom) - y);
|
|
|
|
} else if margin_right.contains(&x) {
|
|
|
|
top_distance = top_distance.min(y - right_rect.top);
|
|
|
|
bottom_distance = bottom_distance.min(right_rect.bottom - y);
|
|
|
|
} else {
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
if margin_top.contains(&y) {
|
|
|
|
left_distance = left_distance.min(x - top_rect.left);
|
|
|
|
right_distance = right_distance.min(top_rect.right - x);
|
|
|
|
} else if shared_y.contains(&y) {
|
|
|
|
left_distance = left_distance.min(x - bottom_rect.left.min(top_rect.left));
|
|
|
|
right_distance = right_distance.min(bottom_rect.right.max(top_rect.right) - x);
|
|
|
|
} else if margin_bottom.contains(&y) {
|
|
|
|
left_distance = left_distance.min(x - bottom_rect.left);
|
|
|
|
right_distance = right_distance.min(bottom_rect.right - x);
|
|
|
|
} else {
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Handle properly
|
|
|
|
assert_ne!(top_distance, SIZE);
|
|
|
|
|
|
|
|
Some(MoveDistances {
|
|
|
|
left: left_distance,
|
|
|
|
right: right_distance,
|
|
|
|
up: top_distance,
|
|
|
|
down: bottom_distance
|
|
|
|
})
|
|
|
|
}
|