Add a bot for one-upping solutions from the leaderboard
This commit is contained in:
parent
9b02c0799a
commit
8e94bcccb4
4 changed files with 1339 additions and 11 deletions
1205
Cargo.lock
generated
1205
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
@ -14,6 +14,11 @@ itertools = "0.10.0"
|
|||
rusqlite = "0.24.2"
|
||||
regex = "1.4.3"
|
||||
rayon = "1.5.0"
|
||||
soup = "0.5.0"
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11.0"
|
||||
features = ["blocking"]
|
||||
|
||||
[[bin]]
|
||||
name = "prague"
|
||||
|
@ -29,4 +34,8 @@ path = "src/combine-layouts.rs"
|
|||
|
||||
[[bin]]
|
||||
name = "optimize-subcity"
|
||||
path = "src/optimize-subcity.rs"
|
||||
path = "src/optimize-subcity.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "upload-bot"
|
||||
path = "src/upload-bot.rs"
|
|
@ -109,7 +109,7 @@ fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<House>)
|
|||
Ok(Rectangle { left, right, top, bottom })
|
||||
}
|
||||
|
||||
fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectangle, RectangleSearchError> {
|
||||
pub 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;
|
||||
|
||||
|
|
132
src/upload-bot.rs
Normal file
132
src/upload-bot.rs
Normal file
|
@ -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 a new issue