Jirka Sejkora
4 years ago
4 changed files with 1378 additions and 50 deletions
File diff suppressed because it is too large
@ -0,0 +1,132 @@ |
|||||
|
use city::{Rectangle, HOUSE_RANGE, House, HouseLayout, City}; |
||||
|
use db::SqliteLayoutDB; |
||||
|
use itertools::Itertools; |
||||
|
use soup::prelude::*; |
||||
|
use std::fmt::Write; |
||||
|
use std::io::Error; |
||||
|
use std::thread::sleep; |
||||
|
use std::time::Duration; |
||||
|
use std::process::exit; |
||||
|
|
||||
|
mod optimization; |
||||
|
mod city; |
||||
|
mod db; |
||||
|
|
||||
|
fn main() { |
||||
|
let args: Vec<_> = std::env::args().collect(); |
||||
|
let api_token = match args.get(1) { |
||||
|
Some(token) => token, |
||||
|
None => { |
||||
|
eprintln!("No api_token passed as first arg!"); |
||||
|
exit(1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
let db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB"); |
||||
|
eprintln!("Loaded the DB, {} stored layouts", db.layouts().len()); |
||||
|
|
||||
|
let city = City::read_from_file("01.in", city::INPUT_CITY_WIDTH, city::INPUT_CITY_HEIGHT); |
||||
|
eprintln!("Loaded the city file, {} houses", city.get_house_count()); |
||||
|
|
||||
|
loop { |
||||
|
if let Some((contestant, score)) = get_top_score() { |
||||
|
eprintln!("Top score found: {} by {}", score, contestant); |
||||
|
|
||||
|
if contestant != "Jirka Sejkora (org)" { |
||||
|
eprintln!("Wrong contestant, trying to one-up."); |
||||
|
let target_price = score - 1; |
||||
|
if let Some(houses) = get_worsened_layout(&db, &city, target_price) { |
||||
|
assert_eq!(city::get_price(&city, &houses), target_price); |
||||
|
eprint!("Found a layout for price {}, uploading...", target_price); |
||||
|
upload_layout(&houses, &api_token); |
||||
|
eprintln!(" done"); |
||||
|
} else { |
||||
|
eprintln!("Could not find a worse layout for price {}!", target_price); |
||||
|
eprintln!("Stopping because there is no solution with this DB."); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
eprintln!("Failed to get top score!") |
||||
|
} |
||||
|
|
||||
|
sleep(Duration::from_secs(5)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn upload_layout(houses: &Vec<House>, api_token: &str) { |
||||
|
let mut body = String::new(); |
||||
|
writeln!(body, "{}", houses.len()).unwrap(); |
||||
|
for house in houses { |
||||
|
writeln!(body, "{} {}", house.y, house.x).unwrap(); |
||||
|
} |
||||
|
|
||||
|
reqwest::blocking::Client::new() |
||||
|
.post("https://ksp.mff.cuni.cz/api/tasks/submit?task=33-3-4&subtask=1") |
||||
|
.bearer_auth(api_token) |
||||
|
.header("content-type", "text/plain") |
||||
|
.body(body) |
||||
|
.send() |
||||
|
.expect("Failed to submit layout"); |
||||
|
} |
||||
|
|
||||
|
fn get_top_score() -> Option<(String, u32)> { |
||||
|
let response = match reqwest::blocking::get("https://ksp.mff.cuni.cz/h/ulohy/33/33-3-4/vysledky") { |
||||
|
Ok(r) => r, |
||||
|
Err(_) => return None, |
||||
|
}; |
||||
|
let soup = match Soup::from_reader(response) { |
||||
|
Ok(soup) => soup, |
||||
|
Err(_) => return None, |
||||
|
}; |
||||
|
let table = soup.tag("table").find_all().filter(|x| if let Some(class) = x.get("class") { class == "scoretable"} else { false }).next().expect("Could not find table with scores."); |
||||
|
let tbody = table.tag("tbody").find().expect("Could not find tbody."); |
||||
|
let tr = tbody.tag("tr").find().expect("Could not find tr."); |
||||
|
let tds: Vec<_> = tr.tag("td").find_all().collect(); |
||||
|
let name = tds[1].text(); |
||||
|
let score_text = tds[3].text(); |
||||
|
let score: u32 = score_text.split_whitespace().next().expect("Failed to split score").parse().expect("Failed to parse score"); |
||||
|
|
||||
|
Some((name, score)) |
||||
|
} |
||||
|
|
||||
|
fn get_worsened_layout(db: &SqliteLayoutDB, city: &City, target_price: u32) -> Option<Vec<House>> { |
||||
|
let better_layouts: Vec<_> = db.layouts().iter() |
||||
|
.sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) |
||||
|
.take_while(|x| city::get_price(&city, x.houses()) < target_price) |
||||
|
.collect(); |
||||
|
|
||||
|
// Try all better layouts, start with a layout as close to the target price as possible
|
||||
|
for db_layout in better_layouts.iter().rev() { |
||||
|
let mut layout = HouseLayout::new(&city); |
||||
|
for house in db_layout.houses() { |
||||
|
layout.add_house(*house); |
||||
|
} |
||||
|
|
||||
|
// Try to find a single house that can be moved to a worse valid position to reach the price
|
||||
|
for (house_i, &house) in layout.houses().iter().enumerate() { |
||||
|
if let Ok(move_rectangle) = optimization::get_valid_move_rectangle(&layout, house) { |
||||
|
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) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
let new_price = layout.price() |
||||
|
+ layout.city.get_price_xy(new_x, new_y) as u32 |
||||
|
- layout.city.get_price(house) as u32; |
||||
|
|
||||
|
if new_price == target_price { |
||||
|
layout.remove_house(house_i); |
||||
|
layout.add_house(House::new(new_x, new_y)); |
||||
|
assert!(layout.is_valid()); |
||||
|
return Some(layout.houses().clone()) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
None |
||||
|
} |
Loading…
Reference in new issue