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 ;
use std ::time ::Duration ;
use chrono ::Local ;
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 ( ) {
let time = Local ::now ( ) ;
eprintln ! ( "[{}] Top score found: {} by {}" , time . format ( "%H:%M:%S" ) , 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
}