2021-02-13 14:43:06 +01:00
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 ::process ::exit ;
2021-02-14 15:32:02 +01:00
use std ::time ::Duration ;
use chrono ::Local ;
2021-02-13 14:43:06 +01:00
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 ( ) {
2021-02-14 15:32:02 +01:00
let time = Local ::now ( ) ;
eprintln! ( " [ {} ] Top score found: {} by {} " , time . format ( " %H:%M:%S " ) , score , contestant ) ;
2021-02-13 14:43:06 +01:00
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
}