// Single-file starter using the vendored serde_json dependency from sdk/rust. use serde_json::{json, Map, Value}; use std::collections::HashSet; use std::io::{self, BufRead, Write}; #[derive(Clone,Copy,PartialEq,Eq,Hash)] struct Point {x:i64,y:i64} #[derive(Clone)] struct Ship {id:i64,pos:Point,cargo:i64} fn number(v:&Value,key:&str)->i64{v[key].as_i64().expect("integer field")} fn pos(v:&Value)->Point{Point{x:number(v,"x"),y:number(v,"y")}} fn distance(a:Point,b:Point,size:i64)->i64{let x=(a.x-b.x).abs();let y=(a.y-b.y).abs();x.min(size-x)+y.min(size-y)} fn destination(p:Point,action:&str,size:i64)->Point{let (mut x,mut y)=(p.x,p.y);match action{"N"=>y-=1,"S"=>y+=1,"E"=>x+=1,"W"=>x-=1,_=>{}}Point{x:x.rem_euclid(size),y:y.rem_euclid(size)}} fn decide(input:&Value,returning:&mut HashSet)->Value{ let rules=&input["rules"];let state=&input["state"];let player=number(input,"player");let size=number(rules,"size");let capacity=number(rules,"capacity"); let own=&state["players"][player as usize];let yard=Point{x:own["yard"][0].as_i64().unwrap(),y:own["yard"][1].as_i64().unwrap()}; let mut fleet:Vec=state["ships"].as_array().unwrap().iter().filter(|s|number(s,"owner")==player).map(|s|Ship{id:number(s,"id"),pos:pos(s),cargo:number(s,"cargo")}).collect(); let harbors:Vec=state["sites"].as_array().unwrap().iter().filter(|s|number(s,"owner")==player).map(pos).collect(); fleet.sort_by_key(|s|(-s.cargo,s.id));let mut occupied:HashSet=fleet.iter().map(|s|s.pos).collect();let mut reserved=HashSet::new();let mut ships=Map::new();let mut debug=Vec::new(); for ship in &fleet{ occupied.remove(&ship.pos);let home=*harbors.iter().min_by_key(|h|distance(ship.pos,**h,size)).unwrap(); if ship.pos==home{returning.remove(&ship.id);} if ship.cargo>=capacity*3/4||(ship.cargo>0&&number(rules,"turns")-number(state,"turn")<=distance(ship.pos,home,size)+6){returning.insert(ship.id);} let value=state["resource"][ship.pos.y as usize][ship.pos.x as usize].as_i64().unwrap();let divisor=number(rules,"move_divisor");let fuel=if rules["floor_move_cost"].as_bool().unwrap_or(false){value/divisor}else{(value+divisor-1)/divisor}; let mut chosen="O";let mut best=f64::NEG_INFINITY; for action in ["O","N","E","S","W"]{ let target=destination(ship.pos,action,size);if (action!="O"&&ship.cargobest{best=score;chosen=action;} } let next=destination(ship.pos,chosen,size);reserved.insert(next);ships.insert(ship.id.to_string(),json!(chosen));let goal=if returning.contains(&ship.id){home}else{next}; debug.push(json!({"ship":ship.id,"target":[goal.x,goal.y],"reason":if returning.contains(&ship.id){"Rust: Fracht abliefern"}else{"Rust: Nachbarfeld oder Abbau"}})); } let spawn=fleet.len()<8&&number(state,"turn")*100=number(rules,"ship_cost")&&!reserved.contains(&yard); json!({"type":"orders","ships":ships,"spawn":spawn,"debug":debug}) } fn run()->Result<(),Box>{ let stdin=io::stdin();let mut lines=stdin.lock().lines();let first=lines.next().ok_or("missing init")??;let init:Value=serde_json::from_str(&first)?; if init["type"]!="init"||init["protocol"]!="kobaltmeer-bot-v1"{return Err("unsupported protocol".into());} let _seed=init["seed"].as_u64().ok_or("missing seed")?; let mut writer=io::BufWriter::new(io::stdout().lock());writeln!(writer,"{}",json!({"type":"ready","name":"Rust Sammler"}))?;writer.flush()?; let mut returning=HashSet::new();for line in lines{let input:Value=serde_json::from_str(&line?)?;if input["type"]=="close"{break;}writeln!(writer,"{}",decide(&input,&mut returning))?;writer.flush()?;} Ok(()) } fn main(){if let Err(error)=run(){eprintln!("{error}");std::process::exit(1);}}