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 cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod garbage;
|
pub mod garbage;
|
||||||
|
pub mod output;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
|
|
|
@ -3,7 +3,8 @@ use rand::RngCore as _;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use tidepool::{cli, config};
|
use fish::bot;
|
||||||
|
use tidepool::{cli, config, output, sim};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt::fmt()
|
tracing_subscriber::fmt::fmt()
|
||||||
|
@ -13,50 +14,50 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
match cli::parse() {
|
match cli::parse() {
|
||||||
cli::Mode::Single(args) => single(args),
|
cli::Mode::Single(args) => single(args),
|
||||||
|
cli::Mode::Multi(args) => multi(args),
|
||||||
cli::Mode::Multi(_args) => {
|
|
||||||
panic!("multi-run mode not implemented yet");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn single(args: cli::SingleRun) -> Result<()> {
|
fn single(args: cli::SingleRun) -> Result<()> {
|
||||||
let config = parse_config_file(&args.config_file)?;
|
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 output = simulation(&args.io, &args.data, args.seed, config);
|
||||||
let _ = config;
|
|
||||||
let _ = seed;
|
|
||||||
let output = serde_json::json!({
|
|
||||||
"seed": seed,
|
|
||||||
});
|
|
||||||
|
|
||||||
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) => {
|
Some(path) => {
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
if prompt_yn(&args.io, "output file already exists, overwrite?") {
|
if prompt_yn(&args.io, "output file already exists, overwrite?") {
|
||||||
tracing::debug!("removing {path:?}");
|
tracing::debug!("removing {path:?}");
|
||||||
std::fs::remove_file(&path)?;
|
std::fs::remove_file(path)?;
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("output file already exists, exiting.");
|
tracing::warn!("output file already exists, exiting.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box::new(
|
Box::new(
|
||||||
std::fs::File::create(&path)
|
std::fs::File::create(path)
|
||||||
.with_context(|| format!("error opening output file '{}'", path.display()))?,
|
.with_context(|| format!("error opening output file '{}'", path.display()))?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => Box::new(std::io::stdout()),
|
None => Box::new(std::io::stdout()),
|
||||||
};
|
};
|
||||||
let mut writer = std::io::BufWriter::new(writer);
|
|
||||||
|
|
||||||
|
let pretty = args.output_file.is_none() && !args.io.quiet;
|
||||||
|
if pretty {
|
||||||
serde_json::to_writer_pretty(&mut writer, &output).context("error writing output")?;
|
serde_json::to_writer_pretty(&mut writer, &output).context("error writing output")?;
|
||||||
writeln!(&mut writer).context("error writing output")?;
|
writeln!(&mut writer).context("error writing output")?;
|
||||||
|
} else {
|
||||||
|
serde_json::to_writer(&mut writer, &output).context("error writing output")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
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> {
|
fn parse_config_file(path: &Path) -> Result<config::Config> {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
std::fs::File::open(path)
|
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();
|
std::io::stdin().read_line(&mut user_input).unwrap();
|
||||||
user_input.trim().eq_ignore_ascii_case("y")
|
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