tidepool: run a simulation and generate JSON output
This commit is contained in:
parent
8461fb8379
commit
501c333048
|
@ -1,5 +1,6 @@
|
|||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod garbage;
|
||||
pub mod output;
|
||||
pub mod queue;
|
||||
pub mod sim;
|
||||
|
|
|
@ -3,7 +3,8 @@ use rand::RngCore as _;
|
|||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use tidepool::{cli, config};
|
||||
use fish::bot;
|
||||
use tidepool::{cli, config, output, sim};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::fmt()
|
||||
|
@ -13,50 +14,50 @@ fn main() -> Result<()> {
|
|||
|
||||
match cli::parse() {
|
||||
cli::Mode::Single(args) => single(args),
|
||||
|
||||
cli::Mode::Multi(_args) => {
|
||||
panic!("multi-run mode not implemented yet");
|
||||
}
|
||||
cli::Mode::Multi(args) => multi(args),
|
||||
}
|
||||
}
|
||||
|
||||
fn single(args: cli::SingleRun) -> Result<()> {
|
||||
let config = parse_config_file(&args.config_file)?;
|
||||
let seed = args.seed.unwrap_or_else(|| rand::thread_rng().next_u64());
|
||||
|
||||
// TODO: run a simulation with the bot
|
||||
let _ = config;
|
||||
let _ = seed;
|
||||
let output = serde_json::json!({
|
||||
"seed": seed,
|
||||
});
|
||||
let output = simulation(&args.io, &args.data, args.seed, config);
|
||||
|
||||
let writer: Box<dyn std::io::Write> = match args.output_file {
|
||||
let mut writer: Box<dyn std::io::Write> = match &args.output_file {
|
||||
Some(path) => {
|
||||
if path.is_file() {
|
||||
if prompt_yn(&args.io, "output file already exists, overwrite?") {
|
||||
tracing::debug!("removing {path:?}");
|
||||
std::fs::remove_file(&path)?;
|
||||
std::fs::remove_file(path)?;
|
||||
} else {
|
||||
tracing::warn!("output file already exists, exiting.");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Box::new(
|
||||
std::fs::File::create(&path)
|
||||
std::fs::File::create(path)
|
||||
.with_context(|| format!("error opening output file '{}'", path.display()))?,
|
||||
)
|
||||
}
|
||||
None => Box::new(std::io::stdout()),
|
||||
};
|
||||
let mut writer = std::io::BufWriter::new(writer);
|
||||
|
||||
serde_json::to_writer_pretty(&mut writer, &output).context("error writing output")?;
|
||||
writeln!(&mut writer).context("error writing output")?;
|
||||
let pretty = args.output_file.is_none() && !args.io.quiet;
|
||||
if pretty {
|
||||
serde_json::to_writer_pretty(&mut writer, &output).context("error writing output")?;
|
||||
writeln!(&mut writer).context("error writing output")?;
|
||||
} else {
|
||||
serde_json::to_writer(&mut writer, &output).context("error writing output")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn multi(args: cli::MultiRun) -> Result<()> {
|
||||
let _config = parse_config_file(&args.config_file)?;
|
||||
panic!("multi-run mode not implemented yet")
|
||||
}
|
||||
|
||||
fn parse_config_file(path: &Path) -> Result<config::Config> {
|
||||
let mut contents = String::new();
|
||||
std::fs::File::open(path)
|
||||
|
@ -77,3 +78,68 @@ fn prompt_yn(io: &cli::IoArgs, msg: &str) -> bool {
|
|||
std::io::stdin().read_line(&mut user_input).unwrap();
|
||||
user_input.trim().eq_ignore_ascii_case("y")
|
||||
}
|
||||
|
||||
fn simulation(
|
||||
io: &cli::IoArgs,
|
||||
data: &cli::OutputDataArgs,
|
||||
seed: Option<u64>,
|
||||
config: config::Config,
|
||||
) -> output::Output {
|
||||
// TODO: status signals
|
||||
|
||||
let mut moves = data.list_moves.then(Vec::new);
|
||||
let seed = seed.unwrap_or_else(|| rand::thread_rng().next_u64());
|
||||
|
||||
let sim_opts = sim::Options {
|
||||
goal: config.game.goal,
|
||||
garbage: config.game.rules.min..=config.game.rules.max,
|
||||
previews: config.game.rules.previews,
|
||||
};
|
||||
let mut sim = sim::Simul::new(seed, sim_opts);
|
||||
let mut pieces = 0;
|
||||
|
||||
let time_start = std::time::Instant::now();
|
||||
while sim.lines_left() > 0 {
|
||||
// TODO: progress bar
|
||||
if !io.quiet {
|
||||
eprintln!("#{}, {} left", pieces, sim.lines_left());
|
||||
}
|
||||
|
||||
let (hold, next) = sim.queue();
|
||||
let queue = mino::Queue::new(hold, &next);
|
||||
let mut bot = bot::Bot::new(sim.matrix(), queue);
|
||||
for i in 0..config.bot.iters {
|
||||
bot.think();
|
||||
if i % 1000 == 999 {
|
||||
tracing::debug!("iteration {i}");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(placement) = bot.suggest() {
|
||||
sim.play(placement);
|
||||
pieces += 1;
|
||||
|
||||
if let Some(moves) = moves.as_mut() {
|
||||
moves.push(output::Move {
|
||||
location: placement.into(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let time_end = std::time::Instant::now();
|
||||
|
||||
let profile = data.profile.then(|| output::Profile {
|
||||
time: time_end - time_start,
|
||||
});
|
||||
|
||||
output::Output {
|
||||
seed,
|
||||
cleared: config.game.goal - sim.lines_left(),
|
||||
pieces,
|
||||
config: config.game,
|
||||
moves,
|
||||
profile,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
use mino::srs::PieceType;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::config::GameConfig;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct Output {
|
||||
#[serde(serialize_with = "ser::seed")]
|
||||
pub seed: u64,
|
||||
pub config: GameConfig,
|
||||
pub pieces: usize,
|
||||
pub cleared: usize,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub moves: Option<Vec<Move>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub profile: Option<Profile>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct Move {
|
||||
pub location: Location,
|
||||
// TODO: garbage added
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct Location {
|
||||
#[serde(rename = "type", serialize_with = "ser::piece_type")]
|
||||
pub ty: PieceType,
|
||||
pub x: i16,
|
||||
pub y: i16,
|
||||
pub orientation: Orientation,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Orientation {
|
||||
North,
|
||||
East,
|
||||
South,
|
||||
West,
|
||||
}
|
||||
|
||||
impl From<mino::srs::Piece> for Location {
|
||||
fn from(pc: mino::srs::Piece) -> Self {
|
||||
Self {
|
||||
ty: pc.ty,
|
||||
x: pc.loc.x,
|
||||
y: pc.loc.y,
|
||||
orientation: match pc.loc.r {
|
||||
mino::Rot::N => Orientation::North,
|
||||
mino::Rot::E => Orientation::East,
|
||||
mino::Rot::S => Orientation::South,
|
||||
mino::Rot::W => Orientation::West,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct Profile {
|
||||
#[serde(serialize_with = "ser::seconds")]
|
||||
pub time: Duration,
|
||||
// TODO: pps mean/variance
|
||||
// TODO: timing stats for specific functions/routines
|
||||
// TODO: stats info about top <N> evaluations
|
||||
// TODO: histograms about selected nodes
|
||||
}
|
||||
|
||||
mod ser {
|
||||
use super::*;
|
||||
|
||||
pub fn seed<S>(seed: &u64, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
// :')
|
||||
seed.to_string().serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn piece_type<S>(ty: &PieceType, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
ty.name().serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn seconds<S>(dur: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
dur.as_secs_f64().serialize(serializer)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue