You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

ms-toollib

Package Overview
Dependencies
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ms-toollib - pypi Package Compare versions

Comparing version
1.4.19
to
1.5.0
+307
base/src/evfs.rs
use crate::videos::{ErrReadVideoReason, EvfVideo, NewSomeVideo, NewSomeVideo2};
use crate::videos::byte_reader::ByteReader;
#[cfg(any(feature = "py", feature = "rs"))]
use std::fs;
use std::ops::{Index, IndexMut};
#[cfg(any(feature = "py", feature = "rs"))]
use std::path::Path;
use itertools::Itertools;
/// evfs文件
pub struct Evfs {
pub raw_data: Vec<u8>,
pub cells: Vec<EvfsCell>,
/// 解析raw_data的偏移量
pub offset: usize,
}
/// evfs重复结构的一个单元
#[derive(Clone)]
pub struct EvfsCell {
pub evf_video: EvfVideo,
pub checksum: Vec<u8>,
}
impl Evfs {
/// 创建一个空白的evfs文件
pub fn new() -> Self {
Evfs {
raw_data: vec![],
cells: vec![],
offset: 0,
}
}
/// 解析已有的evfs文件的二进制数据
pub fn new_with_data(data: Vec<u8>) -> Self {
Evfs {
raw_data: data,
cells: vec![],
offset: 0,
}
}
/// 向末尾追加录像的二进制数据,文件名不要带后缀
pub fn append(&mut self, data: Vec<u8>, file_name: &str, checksum: Vec<u8>) {
self.cells.push(EvfsCell {
evf_video: <EvfVideo as NewSomeVideo2<Vec<u8>, &str>>::new(data, file_name),
checksum,
});
}
/// 删除最后一个录像
pub fn pop(&mut self) {
self.cells.pop();
}
pub fn len(&self) -> usize {
self.cells.len()
}
pub fn is_empty(&self) -> bool {
self.cells.is_empty()
}
pub fn clear(&mut self) {
self.cells.clear();
}
/// 初步验证evfs文件的有效性。适用于网页前端,并不严格。
pub fn is_valid(&mut self) -> bool {
if self.cells.is_empty() {
return false;
}
for cell in self.cells.iter_mut() {
if !cell.evf_video.data.can_analyse {
if cell.evf_video.parse_video().is_err() {
return false;
}
}
}
if !self.cells.iter().map(|c| c.evf_video.version).all_equal() {
return false;
}
if !self
.cells
.iter()
.map(|c| &c.evf_video.data.software)
.all_equal()
{
return false;
}
if !self
.cells
.iter()
.map(|c| &c.evf_video.data.country)
.all_equal()
{
return false;
}
if !self
.cells
.iter()
.map(|c| &c.evf_video.data.player_identifier)
.all_equal()
{
return false;
}
if !self
.cells
.iter()
.map(|c| &c.evf_video.data.race_identifier)
.all_equal()
{
return false;
}
if !self
.cells
.iter()
.map(|c| &c.evf_video.data.uniqueness_identifier)
.all_equal()
{
return false;
}
// 验证时间递增
if self.cells[0].evf_video.data.start_time > self.cells[0].evf_video.data.end_time {
return false;
}
if self.cells.len() > 1 {
for i in 1..self.cells.len() {
if self.cells[i - 1].evf_video.data.end_time
> self.cells[i].evf_video.data.start_time
{
return false;
}
if self.cells[i].evf_video.data.start_time > self.cells[i].evf_video.data.end_time {
return false;
}
}
}
if !self.cells.iter().all(|c| {
c.evf_video.data.is_fair
&& c.evf_video.version >= 4
&& !c.evf_video.data.checksum.is_empty()
}) {
return false;
}
true
}
pub fn get_software(&self) -> &str {
&self.cells[0].evf_video.data.software
}
pub fn get_evf_version(&self) -> u8 {
self.cells[0].evf_video.version
}
pub fn get_start_time(&self) -> u64 {
self.cells[0].evf_video.data.start_time
}
pub fn get_end_time(&self) -> u64 {
self.cells.last().unwrap().evf_video.data.end_time
}
/// 生成evfs_v0文件的二进制数据
pub fn generate_evfs_v0_raw_data(&mut self) {
if self.cells.is_empty() {
return;
}
if !self.cells.iter().map(|c| c.checksum.len()).all_equal() {
panic!("Evfs cells have different checksum lengths");
}
self.raw_data = vec![0];
let chechsum_len = self.cells[0].checksum.len() as u16;
self.raw_data.push((chechsum_len >> 8).try_into().unwrap());
self.raw_data.push((chechsum_len % 256).try_into().unwrap());
for cell in self.cells.iter_mut() {
self.raw_data
.append(&mut cell.evf_video.file_name.clone().into_bytes());
self.raw_data.push(0);
let evf_raw_data = cell.evf_video.data.get_raw_data().unwrap();
let evf_size = evf_raw_data.len() as u32;
self.raw_data.push((evf_size >> 24).try_into().unwrap());
self.raw_data.push((evf_size >> 16).try_into().unwrap());
self.raw_data.push((evf_size >> 8).try_into().unwrap());
self.raw_data.push((evf_size % 256).try_into().unwrap());
self.raw_data.extend_from_slice(&evf_raw_data);
self.raw_data.extend_from_slice(&cell.checksum);
}
}
pub fn parse(&mut self) -> Result<(), ErrReadVideoReason> {
let version = self.get_u8()?;
match version {
0 => self.parse_v0()?,
_ => {},
}
for cell in self.cells.iter_mut() {
if !cell.evf_video.data.can_analyse {
cell.evf_video.parse_video()?;
}
}
Ok(())
}
/// 0.0-0.1版本
fn parse_v0(&mut self) -> Result<(), ErrReadVideoReason> {
let checksum_len = self.get_u16()?;
while self.offset < self.raw_data.len() - 1 {
let file_name = self.get_utf8_c_string('\0')?;
let file_size = self.get_u32()?;
let evf_data = self.get_buffer(file_size as usize)?;
let checksum = self.get_buffer(checksum_len)?;
self.cells.push(EvfsCell {
evf_video: <EvfVideo as NewSomeVideo2<Vec<u8>, &str>>::new(evf_data, &file_name),
checksum,
});
}
Ok(())
}
}
impl ByteReader for Evfs {
fn raw_data(&self) -> &[u8] {
&self.raw_data
}
fn offset_mut(&mut self) -> &mut usize {
&mut self.offset
}
}
#[cfg(any(feature = "py", feature = "rs"))]
impl Evfs {
/// 解析已有的evfs文件的二进制数据
pub fn new_with_file(filename: &str) -> Self {
let data = fs::read(filename).unwrap();
Evfs {
raw_data: data,
cells: vec![],
offset: 0,
}
}
/// 将evfs中的所有录像保存到指定目录,文件名为原文件名加上.evf后缀
pub fn save_evf_files(&self, dir: &str) {
let path = Path::new(dir);
for cell in self.cells.iter() {
cell.evf_video.data.save_to_evf_file(
path.join(cell.evf_video.file_name.clone())
.to_str()
.unwrap(),
);
}
}
/// 将单个evfs文件保存到指定目录(绝对路径),文件名为原文件名加上.evfs后缀
/// 重复文件,xxx.evfs变成xxx(2).evfs
pub fn save_evfs_file(&self, file_name: &str) -> String {
if self.raw_data.is_empty() {
panic!("Evfs raw data is empty, please generate it first.");
}
let file_exist =
std::path::Path::new((file_name.to_string() + &(".evfs".to_string())).as_str())
.exists();
if !file_exist {
fs::write(
(file_name.to_string() + &(".evfs".to_string())).as_str(),
&self.raw_data,
)
.unwrap();
return (file_name.to_string() + &(".evfs".to_string()))
.as_str()
.to_string();
} else {
let mut id = 2;
let mut format_name;
loop {
format_name = file_name.to_string() + &(format!("({}).evfs", id).to_string());
let new_file_name = format_name.as_str();
let file_exist = std::path::Path::new(new_file_name).exists();
if !file_exist {
fs::write(new_file_name, &self.raw_data).unwrap();
return new_file_name.to_string();
}
id += 1;
}
}
}
}
// 为 Evfs 实现 Index trait,使其支持不可变索引(只读访问)
impl Index<usize> for Evfs {
type Output = EvfsCell;
fn index(&self, index: usize) -> &Self::Output {
&self.cells[index]
}
}
// 为 Evfs 实现 IndexMut trait,使其支持可变索引(可修改访问)
impl IndexMut<usize> for Evfs {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.cells[index]
}
}
impl Index<std::ops::Range<usize>> for Evfs {
type Output = [EvfsCell];
fn index(&self, index: std::ops::Range<usize>) -> &Self::Output {
let cells = &self.cells[index.start..index.end];
cells
}
}
use crate::miscellaneous::s_to_ms;
use crate::videos::{BaseVideo, Event};
use crate::safe_board::BoardSize;
use std::cmp::{max, min};
#[cfg(any(feature = "py", feature = "rs"))]
use std::fs;
// BaseVideo按照evf标准,生成evf录像文件的方法
// 和文件操作相关的一些方法
#[cfg(any(feature = "py", feature = "rs"))]
impl<T> BaseVideo<T> {
/// 按evf v0.0-0.1标准,编码出原始二进制数据
pub fn generate_evf_v0_raw_data(&mut self)
where
T: std::ops::Index<usize> + BoardSize,
T::Output: std::ops::Index<usize, Output = i32>,
{
self.raw_data = vec![0, 0];
if self.is_completed {
self.raw_data[1] |= 0b1000_0000;
}
if self.is_official {
self.raw_data[1] |= 0b0100_0000;
}
if self.is_fair {
self.raw_data[1] |= 0b0010_0000;
}
self.raw_data.push(self.height as u8);
self.raw_data.push(self.width as u8);
self.raw_data.push((self.mine_num >> 8).try_into().unwrap());
self.raw_data
.push((self.mine_num % 256).try_into().unwrap());
self.raw_data.push(self.cell_pixel_size);
self.raw_data.push((self.mode >> 8).try_into().unwrap());
self.raw_data.push((self.mode % 256).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv >> 8).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv % 256).try_into().unwrap());
// println!("fff: {:?}", self.game_dynamic_params.rtime_ms);
self.raw_data.push(
(self.game_dynamic_params.rtime_ms >> 16)
.try_into()
.unwrap(),
);
self.raw_data.push(
((self.game_dynamic_params.rtime_ms >> 8) % 256)
.try_into()
.unwrap(),
);
self.raw_data.push(
(self.game_dynamic_params.rtime_ms % 256)
.try_into()
.unwrap(),
);
self.raw_data
.append(&mut self.software.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.player_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.race_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.uniqueness_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.start_time.to_string().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.end_time.to_string().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.country.to_string().into_bytes());
self.raw_data.push(0);
let mut byte = 0;
let mut ptr = 0;
for i in 0..self.height {
for j in 0..self.width {
byte <<= 1;
if self.board[i][j] == -1 {
byte |= 1;
}
ptr += 1;
if ptr == 8 {
self.raw_data.push(byte);
ptr = 0;
byte = 0;
}
}
}
if ptr > 0 {
byte <<= 8 - ptr;
self.raw_data.push(byte);
}
for vas in &self.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &vas.event {
// println!("{:?}: '{:?}', ({:?}, {:?})", event.time, event.mouse.as_str(), event.x, event.y);
match mouse_event.mouse.as_str() {
"mv" => self.raw_data.push(1),
"lc" => self.raw_data.push(2),
"lr" => self.raw_data.push(3),
"rc" => self.raw_data.push(4),
"rr" => self.raw_data.push(5),
"mc" => self.raw_data.push(6),
"mr" => self.raw_data.push(7),
"pf" => self.raw_data.push(8),
"cc" => self.raw_data.push(9),
// 不可能出现,出现再说
_ => self.raw_data.push(99),
}
let t_ms = s_to_ms(vas.time);
self.raw_data.push((t_ms >> 16).try_into().unwrap());
self.raw_data.push(((t_ms >> 8) % 256).try_into().unwrap());
self.raw_data.push((t_ms % 256).try_into().unwrap());
self.raw_data.push((mouse_event.x >> 8).try_into().unwrap());
self.raw_data
.push((mouse_event.x % 256).try_into().unwrap());
self.raw_data.push((mouse_event.y >> 8).try_into().unwrap());
self.raw_data
.push((mouse_event.y % 256).try_into().unwrap());
}
}
if !self.checksum.is_empty() {
self.raw_data.push(0);
self.raw_data
.append(&mut self.checksum.clone().to_vec().to_owned());
} else {
self.raw_data.push(255);
}
}
/// 按evf v0.2标准,编码出原始二进制数据
pub fn generate_evf_v2_raw_data(&mut self)
where
T: std::ops::Index<usize> + BoardSize,
T::Output: std::ops::Index<usize, Output = i32>,
{
self.raw_data = vec![0, 0];
if self.is_completed {
self.raw_data[1] |= 0b1000_0000;
}
if self.is_official {
self.raw_data[1] |= 0b0100_0000;
}
if self.is_fair {
self.raw_data[1] |= 0b0010_0000;
}
self.raw_data.push(self.height as u8);
self.raw_data.push(self.width as u8);
self.raw_data.push((self.mine_num >> 8).try_into().unwrap());
self.raw_data
.push((self.mine_num % 256).try_into().unwrap());
self.raw_data.push(self.cell_pixel_size);
self.raw_data.push((self.mode >> 8).try_into().unwrap());
self.raw_data.push((self.mode % 256).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv >> 8).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv % 256).try_into().unwrap());
// println!("fff: {:?}", self.game_dynamic_params.rtime_ms);
self.raw_data.push(
(self.game_dynamic_params.rtime_ms >> 16)
.try_into()
.unwrap(),
);
self.raw_data.push(
((self.game_dynamic_params.rtime_ms >> 8) % 256)
.try_into()
.unwrap(),
);
self.raw_data.push(
(self.game_dynamic_params.rtime_ms % 256)
.try_into()
.unwrap(),
);
self.raw_data
.append(&mut self.software.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.player_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.race_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.uniqueness_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.start_time.to_string().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.end_time.to_string().into_bytes());
self.raw_data.push(0);
self.raw_data.append(&mut self.country.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.device_uuid.clone().to_owned());
self.raw_data.push(0);
let mut byte = 0;
let mut ptr = 0;
for i in 0..self.height {
for j in 0..self.width {
byte <<= 1;
if self.board[i][j] == -1 {
byte |= 1;
}
ptr += 1;
if ptr == 8 {
self.raw_data.push(byte);
ptr = 0;
byte = 0;
}
}
}
if ptr > 0 {
byte <<= 8 - ptr;
self.raw_data.push(byte);
}
for vas in &self.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &vas.event {
// println!("{:?}: '{:?}', ({:?}, {:?})", event.time, event.mouse.as_str(), event.x, event.y);
match mouse_event.mouse.as_str() {
"mv" => self.raw_data.push(1),
"lc" => self.raw_data.push(2),
"lr" => self.raw_data.push(3),
"rc" => self.raw_data.push(4),
"rr" => self.raw_data.push(5),
"mc" => self.raw_data.push(6),
"mr" => self.raw_data.push(7),
"pf" => self.raw_data.push(8),
"cc" => self.raw_data.push(9),
// 不可能出现,出现再说
_ => self.raw_data.push(99),
}
let t_ms = s_to_ms(vas.time);
self.raw_data.push((t_ms >> 16).try_into().unwrap());
self.raw_data.push(((t_ms >> 8) % 256).try_into().unwrap());
self.raw_data.push((t_ms % 256).try_into().unwrap());
self.raw_data.push((mouse_event.x >> 8).try_into().unwrap());
self.raw_data
.push((mouse_event.x % 256).try_into().unwrap());
self.raw_data.push((mouse_event.y >> 8).try_into().unwrap());
self.raw_data
.push((mouse_event.y % 256).try_into().unwrap());
}
}
if !self.checksum.is_empty() {
self.raw_data.push(0);
self.raw_data
.append(&mut self.checksum.clone().to_vec().to_owned());
} else {
self.raw_data.push(255);
}
}
/// 按evf v0.3标准,编码出原始二进制数据
pub fn generate_evf_v3_raw_data(&mut self)
where
T: std::ops::Index<usize> + BoardSize,
T::Output: std::ops::Index<usize, Output = i32>,
{
self.raw_data = vec![3, 0, 0];
if self.is_completed {
self.raw_data[1] |= 0b1000_0000;
}
if self.is_official {
self.raw_data[1] |= 0b0100_0000;
}
if self.is_fair {
self.raw_data[1] |= 0b0010_0000;
}
if self.get_right() == 0 {
self.raw_data[1] |= 0b0001_0000;
}
if self.use_question {
self.raw_data[2] |= 0b1000_0000;
}
if self.use_cursor_pos_lim {
self.raw_data[2] |= 0b0100_0000;
}
if self.use_auto_replay {
self.raw_data[2] |= 0b0010_0000;
}
self.raw_data.push(self.height as u8);
self.raw_data.push(self.width as u8);
self.raw_data.push((self.mine_num >> 8).try_into().unwrap());
self.raw_data
.push((self.mine_num % 256).try_into().unwrap());
self.raw_data.push(self.cell_pixel_size);
self.raw_data.push((self.mode >> 8).try_into().unwrap());
self.raw_data.push((self.mode % 256).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv >> 8).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv % 256).try_into().unwrap());
// println!("fff: {:?}", self.game_dynamic_params.rtime_ms);
self.raw_data.push(
(self.game_dynamic_params.rtime_ms >> 16)
.try_into()
.unwrap(),
);
self.raw_data.push(
((self.game_dynamic_params.rtime_ms >> 8) % 256)
.try_into()
.unwrap(),
);
self.raw_data.push(
(self.game_dynamic_params.rtime_ms % 256)
.try_into()
.unwrap(),
);
self.raw_data
.append(&mut self.software.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.player_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.race_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.uniqueness_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.start_time.to_string().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.end_time.to_string().into_bytes());
self.raw_data.push(0);
self.raw_data.append(&mut self.country.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.device_uuid.clone().to_owned());
self.raw_data.push(0);
let mut byte = 0;
let mut ptr = 0;
for i in 0..self.height {
for j in 0..self.width {
byte <<= 1;
if self.board[i][j] == -1 {
byte |= 1;
}
ptr += 1;
if ptr == 8 {
self.raw_data.push(byte);
ptr = 0;
byte = 0;
}
}
}
if ptr > 0 {
byte <<= 8 - ptr;
self.raw_data.push(byte);
}
for vas in &self.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &vas.event {
// println!("{:?}: '{:?}', ({:?}, {:?})", event.time, event.mouse.as_str(), event.x, event.y);
match mouse_event.mouse.as_str() {
"mv" => self.raw_data.push(1),
"lc" => self.raw_data.push(2),
"lr" => self.raw_data.push(3),
"rc" => self.raw_data.push(4),
"rr" => self.raw_data.push(5),
"mc" => self.raw_data.push(6),
"mr" => self.raw_data.push(7),
"pf" => self.raw_data.push(8),
"cc" => self.raw_data.push(9),
// 不可能出现,出现再说
_ => self.raw_data.push(99),
}
let t_ms = s_to_ms(vas.time);
self.raw_data.push((t_ms >> 16).try_into().unwrap());
self.raw_data.push(((t_ms >> 8) % 256).try_into().unwrap());
self.raw_data.push((t_ms % 256).try_into().unwrap());
self.raw_data.push((mouse_event.x >> 8).try_into().unwrap());
self.raw_data
.push((mouse_event.x % 256).try_into().unwrap());
self.raw_data.push((mouse_event.y >> 8).try_into().unwrap());
self.raw_data
.push((mouse_event.y % 256).try_into().unwrap());
}
}
if !self.checksum.is_empty() {
self.raw_data.push(0);
self.raw_data
.append(&mut self.checksum.clone().to_vec().to_owned());
} else {
self.raw_data.push(255);
}
}
/// 按evf v4标准,编码出原始二进制数据
/// v4开始,判断nf的标准发生了变化!
pub fn generate_evf_v4_raw_data(&mut self)
where
T: std::ops::Index<usize> + BoardSize,
T::Output: std::ops::Index<usize, Output = i32>,
{
assert!(self.height <= 255);
assert!(self.width <= 255);
assert!(self.height * self.cell_pixel_size as usize <= 32767);
assert!(self.width * self.cell_pixel_size as usize <= 32767);
assert!(self.mine_num <= 65535);
self.raw_data = vec![4, 0, 0];
if self.is_completed {
self.raw_data[1] |= 0b1000_0000;
}
if self.is_official {
self.raw_data[1] |= 0b0100_0000;
}
if self.is_fair {
self.raw_data[1] |= 0b0010_0000;
}
if self.get_rce().unwrap() == 0 {
self.raw_data[1] |= 0b0001_0000;
}
if self.translated {
self.raw_data[1] |= 0b0000_1000;
}
if self.use_question {
self.raw_data[2] |= 0b1000_0000;
}
if self.use_cursor_pos_lim {
self.raw_data[2] |= 0b0100_0000;
}
if self.use_auto_replay {
self.raw_data[2] |= 0b0010_0000;
}
self.raw_data.push(self.height as u8);
self.raw_data.push(self.width as u8);
self.raw_data.push((self.mine_num >> 8).try_into().unwrap());
self.raw_data
.push((self.mine_num % 256).try_into().unwrap());
self.raw_data.push(self.cell_pixel_size);
self.raw_data.push((self.mode >> 8).try_into().unwrap());
self.raw_data.push((self.mode % 256).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv >> 8).try_into().unwrap());
self.raw_data
.push((self.static_params.bbbv % 256).try_into().unwrap());
self.raw_data
.extend_from_slice(&self.game_dynamic_params.rtime_ms.to_be_bytes());
if self.country.len() < 2 {
self.raw_data.extend("XX".as_bytes());
} else {
let first_char = self.country.chars().nth(0).unwrap();
let second_char = self.country.chars().nth(1).unwrap();
if first_char.is_ascii_alphabetic() && second_char.is_ascii_alphabetic() {
self.raw_data.push(first_char.to_ascii_uppercase() as u8);
self.raw_data.push(second_char.to_ascii_uppercase() as u8);
} else {
self.raw_data.extend("XX".as_bytes());
}
}
self.raw_data
.extend_from_slice(&self.start_time.to_be_bytes());
self.raw_data
.extend_from_slice(&self.end_time.to_be_bytes());
self.raw_data
.append(&mut self.software.clone().into_bytes());
self.raw_data.push(0);
if self.translated {
self.raw_data
.append(&mut self.translate_software.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.original_encoding.clone().into_bytes());
self.raw_data.push(0);
}
self.raw_data
.append(&mut self.player_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.race_identifier.clone().into_bytes());
self.raw_data.push(0);
self.raw_data
.append(&mut self.uniqueness_identifier.clone().into_bytes());
self.raw_data.push(0);
let device_uuid_length = self.device_uuid.len() as u16;
self.raw_data
.extend_from_slice(&device_uuid_length.to_be_bytes());
self.raw_data
.append(&mut self.device_uuid.clone().to_owned());
let mut byte = 0;
let mut ptr = 0;
for i in 0..self.height {
for j in 0..self.width {
byte <<= 1;
if self.board[i][j] == -1 {
byte |= 1;
}
ptr += 1;
if ptr == 8 {
self.raw_data.push(byte);
ptr = 0;
byte = 0;
}
}
}
if ptr > 0 {
byte <<= 8 - ptr;
self.raw_data.push(byte);
}
// 自定义指标的数量
self.raw_data.push(0);
self.raw_data.push(0);
let vas_0 = &self.video_action_state_recorder[0];
// 计算鼠标坐标差值使用
let mut last_mouse_event;
let mut last_mouse_event_time;
if let Some(Event::Mouse(event_0)) = &vas_0.event {
last_mouse_event = event_0;
last_mouse_event_time = vas_0.time;
match event_0.mouse.as_str() {
"mv" => self.raw_data.push(1),
"lc" => self.raw_data.push(2),
"rc" => self.raw_data.push(4),
"pf" => self.raw_data.push(8),
// 不可能出现,出现再说
_ => panic!(""),
}
let t_ms = s_to_ms(vas_0.time) as u8;
self.raw_data.push((t_ms).try_into().unwrap());
self.raw_data.push((event_0.x >> 8).try_into().unwrap());
self.raw_data.push((event_0.x % 256).try_into().unwrap());
self.raw_data.push((event_0.y >> 8).try_into().unwrap());
self.raw_data.push((event_0.y % 256).try_into().unwrap());
} else {
panic!("");
}
for event_id in 1..self.video_action_state_recorder.len() {
let vas = &self.video_action_state_recorder[event_id];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
// println!("{:?}: '{:?}', ({:?}, {:?})", event.time, event.mouse.as_str(), event.x, event.y);
let mut delta_t = s_to_ms(vas.time) - s_to_ms(last_mouse_event_time);
while delta_t > 255 {
self.raw_data.push(255);
let pause_time = min(65535 as u32, delta_t) as u16;
self.raw_data.extend_from_slice(&pause_time.to_be_bytes());
delta_t -= pause_time as u32;
}
match mouse_event.mouse.as_str() {
"mv" => self.raw_data.push(1),
"lc" => self.raw_data.push(2),
"lr" => self.raw_data.push(3),
"rc" => self.raw_data.push(4),
"rr" => self.raw_data.push(5),
"mc" => self.raw_data.push(6),
"mr" => self.raw_data.push(7),
"pf" => self.raw_data.push(8),
"cc" => self.raw_data.push(9),
"l" => self.raw_data.push(10),
"r" => self.raw_data.push(11),
"m" => self.raw_data.push(12),
// 不可能出现,出现再说
_ => {
continue;
}
}
self.raw_data.push(delta_t as u8);
let delta_x = mouse_event.x as i16 - last_mouse_event.x as i16;
let delta_y = mouse_event.y as i16 - last_mouse_event.y as i16;
self.raw_data.extend_from_slice(&delta_x.to_be_bytes());
self.raw_data.extend_from_slice(&delta_y.to_be_bytes());
last_mouse_event = mouse_event;
last_mouse_event_time = vas.time;
}
}
self.raw_data.push(0);
self.raw_data
.extend_from_slice(&(self.checksum.len() as u16).to_be_bytes());
self.raw_data
.append(&mut self.checksum.clone().to_vec().to_owned());
}
// /// 在二进制数据最后添加checksum。通过generate_evf_v0_raw_data或push_checksum添加checksum二选一。
// /// 若无checksum就用generate_evf_v0_raw_data
// pub fn push_checksum(&mut self, checksum: &mut Vec<u8>) {
// *self.raw_data.last_mut().unwrap() = 0;
// self.raw_data.append(checksum);
// }
/// 存evf文件,自动加后缀,xxx.evf重复变成xxx(2).evf
pub fn save_to_evf_file(&self, file_name: &str) -> String {
if self.raw_data.is_empty() {
panic!(
"Raw data is empty. Please generate raw data by `generate_evf_v4_raw_data` first."
);
}
let file_exist =
std::path::Path::new((file_name.to_string() + &(".evf".to_string())).as_str()).exists();
if !file_exist {
fs::write(
(file_name.to_string() + &(".evf".to_string())).as_str(),
&self.raw_data,
)
.unwrap();
return (file_name.to_string() + &(".evf".to_string()))
.as_str()
.to_string();
} else {
let mut id = 2;
let mut format_name;
loop {
format_name = file_name.to_string() + &(format!("({}).evf", id).to_string());
let new_file_name = format_name.as_str();
let file_exist = std::path::Path::new(new_file_name).exists();
if !file_exist {
fs::write(new_file_name, &self.raw_data).unwrap();
return new_file_name.to_string();
}
id += 1;
}
}
}
}
use crate::miscellaneous::s_to_ms;
use crate::videos::{BaseVideo, Event};
use crate::{GameBoardState, MouseState};
#[cfg(any(feature = "py", feature = "rs"))]
use std::time::Instant;
// BaseVideo指标计算和获取的方法
impl<T> BaseVideo<T> {
// 再实现一些get、set方法
pub fn set_pix_size(&mut self, pix_size: u8) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Ready
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Loss
{
return Err(());
}
self.cell_pixel_size = pix_size;
Ok(0)
}
/// 获取当前录像时刻的后验的游戏局面
pub fn get_game_board(&self) -> Vec<Vec<i32>> {
if self.game_board_state == GameBoardState::Display {
return self.video_action_state_recorder[self.current_event_id]
.next_game_board
.as_ref()
.unwrap()
.borrow()
.game_board
.clone();
} else {
return self.minesweeper_board.game_board.clone();
}
}
/// 获取当前录像时刻的局面概率
pub fn get_game_board_poss(&mut self) -> Vec<Vec<f64>> {
let mut id = self.current_event_id;
loop {
if self.video_action_state_recorder[id].useful_level < 2 {
if id >= 1 {
id -= 1;
}
if id == 0 {
let p = self.mine_num as f64 / (self.height * self.width) as f64;
return vec![vec![p; self.height]; self.width];
}
} else {
// println!("{:?}, {:?}",self.current_event_id, self.video_action_state_recorder.len());
return self.video_action_state_recorder[self.current_event_id]
.next_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_poss()
.clone();
}
}
}
// 录像解析时,设置游戏时间,时间成绩。
// 同时设置秒和毫秒的时间,并且只能写入一次
pub fn set_rtime<U>(&mut self, time: U) -> Result<u8, ()>
where
U: Into<f64>,
{
if !self.allow_set_rtime {
return Err(());
}
let time = time.into();
self.game_dynamic_params.rtime = time;
self.game_dynamic_params.rtime_ms = s_to_ms(time);
self.allow_set_rtime = false;
Ok(0)
}
/// 用于(游戏时)计数器上显示的时间,和arbiter一致
#[cfg(any(feature = "py", feature = "rs"))]
pub fn get_time(&self) -> f64 {
match self.game_board_state {
GameBoardState::Playing => {
let now = Instant::now();
// return now.duration_since(self.game_start_instant).as_millis() as f64 / 1000.0;
let time_ms = now.duration_since(self.video_start_instant).as_millis() as u32;
return (time_ms - self.game_start_ms) as f64 / 1000.0;
}
GameBoardState::PreFlaging => {
let now = Instant::now();
return now.duration_since(self.video_start_instant).as_millis() as f64 / 1000.0;
}
GameBoardState::Loss | GameBoardState::Win => self.game_dynamic_params.rtime,
GameBoardState::Ready => 0.0,
GameBoardState::Display => self.current_time,
}
}
pub fn get_rtime(&self) -> Result<f64, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Display
{
return Err(());
}
Ok(self.game_dynamic_params.rtime)
}
pub fn get_rtime_ms(&self) -> Result<u32, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Display
{
return Err(());
}
Ok(self.game_dynamic_params.rtime_ms)
}
/// 录像播放器时间的开始值
/// 理论上:video_start_time = -self.delta_time
pub fn get_video_start_time(&self) -> Result<f64, ()> {
if self.game_board_state != GameBoardState::Display {
return Err(());
}
Ok(-self.delta_time)
}
/// 录像播放器时间的结束值
/// 理论上:video_end_time = rtime
pub fn get_video_end_time(&self) -> Result<f64, ()> {
if self.game_board_state != GameBoardState::Display {
return Err(());
}
// end_time的计算方法是特殊的,直接返回rtime,而不是用减法
// 因为此处减法会带来浮点数误差
// Ok(self.video_action_state_recorder.last().unwrap().time - self.delta_time)
Ok(self.game_dynamic_params.rtime)
}
/// 录像播放时,按时间设置current_time;超出两端范围取两端。
/// 游戏时不要调用。
pub fn set_current_time(&mut self, mut time: f64) {
self.current_time = time;
if self.current_time < self.get_video_start_time().unwrap() {
self.current_time = self.get_video_start_time().unwrap()
}
if self.current_time > self.get_video_end_time().unwrap() {
self.current_time = self.get_video_end_time().unwrap()
}
time += self.delta_time;
if time > self.video_action_state_recorder[self.current_event_id].time {
loop {
if self.current_event_id >= self.video_action_state_recorder.len() - 1 {
// 最后一帧
break;
}
self.current_event_id += 1;
if self.video_action_state_recorder[self.current_event_id].time <= time {
continue;
} else {
self.current_event_id -= 1;
break;
}
}
} else {
loop {
if self.current_event_id == 0 {
break;
}
self.current_event_id -= 1;
if self.video_action_state_recorder[self.current_event_id].time > time {
continue;
} else {
break;
}
}
}
// self.current_time =
// self.video_action_state_recorder[self.current_event_id].time - self.delta_time;
}
/// 设置current_event_id
pub fn set_current_event_id(&mut self, id: usize) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Display {
return Err(());
};
self.current_event_id = id;
self.current_time = self.video_action_state_recorder[id].time - self.delta_time;
Ok(0)
}
pub fn set_use_question(&mut self, use_question: bool) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.use_question = use_question;
Ok(0)
}
pub fn set_use_cursor_pos_lim(&mut self, use_cursor_pos_lim: bool) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.use_cursor_pos_lim = use_cursor_pos_lim;
Ok(0)
}
pub fn set_use_auto_replay(&mut self, use_auto_replay: bool) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.use_auto_replay = use_auto_replay;
Ok(0)
}
pub fn set_is_official(&mut self, is_official: bool) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.is_official = is_official;
Ok(0)
}
pub fn set_is_fair(&mut self, is_fair: bool) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.is_fair = is_fair;
Ok(0)
}
/// 可猜模式必须在ready时设置模式,其它模式扫完再设置也可以
pub fn set_mode(&mut self, mode: u16) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Ready
{
return Err(());
};
self.mode = mode;
Ok(0)
}
pub fn set_software(&mut self, software: String) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Ready
{
return Err(());
};
self.software = software;
Ok(0)
}
pub fn set_player_identifier(&mut self, player_identifier: String) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.player_identifier = player_identifier;
Ok(0)
}
pub fn set_race_identifier(&mut self, race_identifier: String) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.race_identifier = race_identifier;
Ok(0)
}
pub fn set_uniqueness_identifier(&mut self, uniqueness_identifier: String) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.uniqueness_identifier = uniqueness_identifier;
Ok(0)
}
/// 拟弃用,会自动记录
// pub fn set_start_time(&mut self, start_time: Vec<u8>) -> Result<u8, ()> {
// if self.game_board_state != GameBoardState::Loss
// && self.game_board_state != GameBoardState::Win
// {
// return Err(());
// };
// self.start_time = start_time;
// Ok(0)
// }
/// 拟弃用,会自动记录
// pub fn set_end_time(&mut self, end_time: Vec<u8>) -> Result<u8, ()> {
// if self.game_board_state != GameBoardState::Loss
// && self.game_board_state != GameBoardState::Win
// {
// return Err(());
// };
// self.end_time = end_time;
// Ok(0)
// }
pub fn set_country(&mut self, country: String) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.country = country;
Ok(0)
}
pub fn set_device_uuid(&mut self, device_uuid: Vec<u8>) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Loss
{
return Err(());
}
self.device_uuid = device_uuid;
Ok(0)
}
/// 在生成二进制数据后,在raw_data里添加checksum
/// 按照evf0-3的标准添加,即删除末尾的/255,添加/0、32位checksum
pub fn set_checksum_evf_v3(&mut self, checksum: Vec<u8>) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
if self.checksum.is_empty() {
*self.raw_data.last_mut().unwrap() = 0;
self.raw_data
.append(&mut checksum.clone().to_vec().to_owned());
self.checksum = checksum;
// self.has_checksum = true;
return Ok(0);
} else {
let ptr = self.raw_data.len() - 32;
for i in 0..32 {
self.raw_data[ptr + i] = checksum[i];
}
return Ok(0);
}
}
/// 在生成二进制数据后,在raw_data里添加checksum
/// 按照evf4的标准添加,即添加u16的长度、若干位checksum
pub fn set_checksum_evf_v4(&mut self, checksum: Vec<u8>) -> Result<u8, ()> {
// avf、evfv3、evfv4的典型高级录像体积对比,单位kB
// 压缩前:64.2,63.9,47.9
// 压缩后(zip):25.4,24.6,6.84
// 压缩后(gzip):25.2,24.7,6.6
// 压缩后(xz-6):10.9,11.1,4.98
if self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Win
{
return Err(());
};
self.raw_data
.truncate(self.raw_data.len() - self.checksum.len() - 2);
self.raw_data
.extend_from_slice(&(checksum.len() as u16).to_be_bytes());
self.raw_data
.append(&mut checksum.clone().to_vec().to_owned());
return Ok(0);
}
pub fn get_raw_data(&self) -> Result<Vec<u8>, ()> {
if self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Display
{
return Err(());
}
Ok(self.raw_data.clone())
}
pub fn get_left(&self) -> usize {
match self.game_board_state {
GameBoardState::Display => {
self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.left
}
_ => self.minesweeper_board.left,
}
}
pub fn get_right(&self) -> usize {
match self.game_board_state {
GameBoardState::Display => {
self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.right
}
_ => self.minesweeper_board.right,
}
}
pub fn get_double(&self) -> usize {
match self.game_board_state {
GameBoardState::Display => {
self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.double
}
_ => self.minesweeper_board.double,
}
}
pub fn get_cl(&self) -> usize {
self.get_left() + self.get_right() + self.get_double()
}
pub fn get_flag(&self) -> usize {
match self.game_board_state {
GameBoardState::Display => {
self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.flag
}
_ => self.minesweeper_board.flag,
}
}
pub fn get_left_s(&self) -> f64 {
match self.game_board_state {
GameBoardState::Display => {
if self.current_time < 0.00099 {
return 0.0;
}
self.get_left() as f64 / self.current_time
}
GameBoardState::Loss | GameBoardState::Win => self.game_dynamic_params.left_s,
GameBoardState::PreFlaging | GameBoardState::Ready => 0.0,
#[cfg(any(feature = "py", feature = "rs"))]
GameBoardState::Playing => {
let now = Instant::now();
let t_ms = now.duration_since(self.video_start_instant).as_millis() as u32;
self.get_left() as f64 * 1000.0 / (t_ms - self.game_start_ms) as f64
}
#[allow(unreachable_patterns)]
#[cfg(any(feature = "js"))]
GameBoardState::Playing => 0.0,
}
}
pub fn get_right_s(&self) -> f64 {
match self.game_board_state {
GameBoardState::Display => {
if self.current_time < 0.00099 {
return 0.0;
}
self.get_right() as f64 / self.current_time
}
GameBoardState::Loss | GameBoardState::Win => self.game_dynamic_params.right_s,
GameBoardState::PreFlaging | GameBoardState::Ready => 0.0,
#[cfg(any(feature = "py", feature = "rs"))]
GameBoardState::Playing => {
let now = Instant::now();
let t_ms = now.duration_since(self.video_start_instant).as_millis() as u32;
self.get_right() as f64 * 1000.0 / (t_ms - self.game_start_ms) as f64
}
#[allow(unreachable_patterns)]
#[cfg(any(feature = "js"))]
GameBoardState::Playing => 0.0,
}
}
pub fn get_double_s(&self) -> f64 {
match self.game_board_state {
GameBoardState::Display => {
if self.current_time < 0.00099 {
return 0.0;
}
self.get_double() as f64 / self.current_time
}
GameBoardState::Loss | GameBoardState::Win => self.game_dynamic_params.double_s,
GameBoardState::PreFlaging | GameBoardState::Ready => 0.0,
#[cfg(any(feature = "py", feature = "rs"))]
GameBoardState::Playing => {
let now = Instant::now();
let t_ms = now.duration_since(self.video_start_instant).as_millis() as u32;
self.get_double() as f64 * 1000.0 / (t_ms - self.game_start_ms) as f64
}
#[allow(unreachable_patterns)]
#[cfg(any(feature = "js"))]
GameBoardState::Playing => 0.0,
}
}
pub fn get_cl_s(&self) -> f64 {
match self.game_board_state {
GameBoardState::Display => {
if self.current_time < 0.00099 {
return 0.0;
}
self.get_cl() as f64 / self.current_time
}
GameBoardState::Loss | GameBoardState::Win => self.game_dynamic_params.cl_s,
GameBoardState::PreFlaging | GameBoardState::Ready => 0.0,
#[cfg(any(feature = "py", feature = "rs"))]
GameBoardState::Playing => {
let now = Instant::now();
let t_ms = now.duration_since(self.video_start_instant).as_millis() as u32;
self.get_cl() as f64 * 1000.0 / (t_ms - self.game_start_ms) as f64
}
#[allow(unreachable_patterns)]
#[cfg(any(feature = "js"))]
GameBoardState::Playing => 0.0,
}
}
pub fn get_flag_s(&self) -> f64 {
match self.game_board_state {
GameBoardState::Display => {
if self.current_time < 0.00099 {
return 0.0;
}
self.get_flag() as f64 / self.current_time
}
GameBoardState::Loss | GameBoardState::Win => self.game_dynamic_params.flag_s,
GameBoardState::PreFlaging | GameBoardState::Ready => 0.0,
#[cfg(any(feature = "py", feature = "rs"))]
GameBoardState::Playing => {
let now = Instant::now();
let t_ms = now.duration_since(self.video_start_instant).as_millis() as u32;
self.get_flag() as f64 * 1000.0 / (t_ms - self.game_start_ms) as f64
}
#[allow(unreachable_patterns)]
#[cfg(any(feature = "js"))]
GameBoardState::Playing => 0.0,
}
}
pub fn get_path(&self) -> f64 {
if self.video_action_state_recorder.is_empty() {
return 0.0;
}
if self.game_board_state == GameBoardState::Display {
self.video_action_state_recorder[self.current_event_id].path
} else {
self.video_action_state_recorder.last().unwrap().path
}
}
pub fn get_etime(&self) -> Result<f64, ()> {
let bbbv_solved = self.get_bbbv_solved()?;
if bbbv_solved == 0 {
return Ok(0.0);
}
if self.game_board_state == GameBoardState::Display {
Ok(self.current_time / bbbv_solved as f64 * self.static_params.bbbv as f64)
} else {
let t = self.game_dynamic_params.rtime;
Ok(t / bbbv_solved as f64 * self.static_params.bbbv as f64)
}
}
pub fn get_bbbv_s(&self) -> Result<f64, ()> {
let bbbv_solved = self.get_bbbv_solved()?;
if self.game_board_state == GameBoardState::Display {
if self.current_time < 0.00099 {
return Ok(0.0);
}
return Ok(bbbv_solved as f64 / self.current_time);
}
Ok(bbbv_solved as f64 / self.game_dynamic_params.rtime)
}
pub fn get_bbbv_solved(&self) -> Result<usize, ()> {
match self.game_board_state {
GameBoardState::Display => Ok(self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.bbbv_solved),
// GameBoardState::Win | GameBoardState::Loss => Ok(self
// .video_dynamic_params
// .bbbv_solved),
GameBoardState::Win | GameBoardState::Loss => Ok(self
.video_action_state_recorder
.last()
.unwrap()
.key_dynamic_params
.bbbv_solved),
_ => Err(()),
}
}
pub fn get_stnb(&self) -> Result<f64, ()> {
let bbbv_solved = self.get_bbbv_solved()?;
if self.game_board_state == GameBoardState::Display && self.current_time < 0.00099 {
return Ok(0.0);
}
let c;
match (self.height, self.width, self.mine_num) {
(8, 8, 10) => c = 47.299,
(16, 16, 40) => c = 153.73,
(16, 30, 99) => c = 435.001,
_ => return Ok(0.0),
}
if self.game_board_state == GameBoardState::Display {
Ok(c * bbbv_solved as f64 / self.current_time.powf(1.7)
* (bbbv_solved as f64 / self.static_params.bbbv as f64).powf(0.5))
} else {
Ok(
c * bbbv_solved as f64 / self.game_dynamic_params.rtime.powf(1.7)
* (bbbv_solved as f64 / self.static_params.bbbv as f64).powf(0.5),
)
}
}
pub fn get_rqp(&self) -> Result<f64, ()> {
let bbbv_solved = self.get_bbbv_solved()?;
if bbbv_solved == 0 {
return Ok(0.0);
}
Ok(self.current_time.powf(2.0) / bbbv_solved as f64)
}
pub fn get_qg(&self) -> Result<f64, ()> {
let bbbv_solved = self.get_bbbv_solved()?;
if bbbv_solved == 0 {
return Ok(0.0);
}
Ok(self.current_time.powf(1.7) / bbbv_solved as f64)
}
pub fn get_lce(&self) -> Result<usize, ()> {
match self.game_board_state {
GameBoardState::Display => Ok(self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.lce),
GameBoardState::Win | GameBoardState::Loss => Ok(self
.video_action_state_recorder
.last()
.unwrap()
.key_dynamic_params
.lce),
_ => Err(()),
}
}
pub fn get_rce(&self) -> Result<usize, ()> {
match self.game_board_state {
GameBoardState::Display => Ok(self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.rce),
GameBoardState::Win | GameBoardState::Loss => Ok(self
.video_action_state_recorder
.last()
.unwrap()
.key_dynamic_params
.rce),
_ => Err(()),
}
}
pub fn get_dce(&self) -> Result<usize, ()> {
match self.game_board_state {
GameBoardState::Display => Ok(self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.dce),
GameBoardState::Win | GameBoardState::Loss => Ok(self
.video_action_state_recorder
.last()
.unwrap()
.key_dynamic_params
.dce),
_ => Err(()),
}
}
pub fn get_ce(&self) -> Result<usize, ()> {
match self.game_board_state {
GameBoardState::Display => {
let p = &self.video_action_state_recorder[self.current_event_id].key_dynamic_params;
Ok(p.lce + p.rce + p.dce)
}
GameBoardState::Win | GameBoardState::Loss => {
let p = &self
.video_action_state_recorder
.last()
.unwrap()
.key_dynamic_params;
Ok(p.lce + p.rce + p.dce)
}
_ => Err(()),
}
}
pub fn get_ce_s(&self) -> Result<f64, ()> {
let ce = self.get_ce()?;
if self.current_time < 0.00099 {
return Ok(0.0);
}
Ok(ce as f64 / self.current_time)
}
pub fn get_corr(&self) -> Result<f64, ()> {
let ce = self.get_ce()?;
let cl = self.get_cl();
if cl == 0 {
return Ok(0.0);
}
Ok(ce as f64 / cl as f64)
}
pub fn get_thrp(&self) -> Result<f64, ()> {
let ce = self.get_ce()?;
let bbbv_solved = self.get_bbbv_solved().unwrap();
if ce == 0 {
return Ok(0.0);
}
Ok(bbbv_solved as f64 / ce as f64)
}
pub fn get_ioe(&self) -> Result<f64, ()> {
let bbbv_solved = self.get_bbbv_solved()?;
let cl = self.get_cl();
if cl == 0 {
return Ok(0.0);
}
Ok(bbbv_solved as f64 / cl as f64)
}
// 未实现
pub fn get_op_solved(&self) -> Result<usize, ()> {
if self.game_board_state != GameBoardState::Display
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Loss
{
return Err(());
};
Ok(self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.op_solved)
}
// 未实现
pub fn get_isl_solved(&self) -> Result<usize, ()> {
if self.game_board_state != GameBoardState::Display
&& self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Loss
{
return Err(());
};
Ok(self.video_action_state_recorder[self.current_event_id]
.key_dynamic_params
.isl_solved)
}
/// 跨语言调用时,不能传递枚举体用这个
pub fn get_mouse_state(&self) -> usize {
let m_s;
if self.game_board_state == GameBoardState::Display {
m_s = self.video_action_state_recorder[self.current_event_id].mouse_state;
} else {
m_s = self.minesweeper_board.mouse_state;
}
match m_s {
MouseState::UpUp => 1,
MouseState::UpDown => 2,
MouseState::UpDownNotFlag => 3,
MouseState::DownUp => 4,
MouseState::Chording => 5,
MouseState::ChordingNotFlag => 6,
MouseState::DownUpAfterChording => 7,
MouseState::Undefined => 8,
}
}
pub fn get_checksum(&self) -> Result<Vec<u8>, ()> {
if self.game_board_state != GameBoardState::Win
&& self.game_board_state != GameBoardState::Loss
&& self.game_board_state != GameBoardState::Display
{
return Err(());
}
Ok(self.checksum.clone())
}
/// 录像播放时,返回鼠标的坐标。
/// 避开局面外的操作(记为最右下角)
pub fn get_x_y(&self) -> Result<(u16, u16), ()> {
if self.game_board_state != GameBoardState::Display {
return Err(());
};
let mut k = 0;
loop {
if let Some(Event::Mouse(mouse_event)) =
&self.video_action_state_recorder[self.current_event_id - k].event
{
if mouse_event.x < self.cell_pixel_size as u16 * self.width as u16 {
return Ok((
(mouse_event.x as f64 * self.video_playing_pix_size_k) as u16,
(mouse_event.y as f64 * self.video_playing_pix_size_k) as u16,
));
}
}
k += 1;
}
}
// 返回录像文件里记录的方格尺寸。flop_new播放器里会用到。这是因为元扫雷和flop播放器的播放机制不同。
pub fn get_pix_size(&self) -> Result<u8, ()> {
if self.game_board_state != GameBoardState::Display {
return Err(());
};
Ok(self.cell_pixel_size)
}
// 录像播放时,设置按何种像素播放,涉及鼠标位置回报
pub fn set_video_playing_pix_size(&mut self, pix_size: u8) {
if self.game_board_state != GameBoardState::Display {
panic!("");
};
self.video_playing_pix_size_k = pix_size as f64 / self.cell_pixel_size as f64;
}
}
use crate::videos::types::ErrReadVideoReason;
use encoding_rs::{GB18030, WINDOWS_1252};
// 实现了文件字节读取的 trait,读取各种整数、字符串,解析阿比特时间戳等
pub trait ByteReader {
/// 返回底层字节切片
fn raw_data(&self) -> &[u8];
/// 返回 offset 的可变引用,用于自动推进
fn offset_mut(&mut self) -> &mut usize;
fn get_u8(&mut self) -> Result<u8, ErrReadVideoReason> {
let offset = *self.offset_mut();
if let Some(&b) = self.raw_data().get(offset) {
*self.offset_mut() += 1;
Ok(b)
} else {
Err(ErrReadVideoReason::FileIsTooShort)
}
}
/// 都是大端法
fn get_u16(&mut self) -> Result<u16, ErrReadVideoReason> {
let a = self.get_u8()?;
let b = self.get_u8()?;
Ok((a as u16) << 8 | (b as u16))
}
fn get_i16(&mut self) -> Result<i16, ErrReadVideoReason> {
let a = self.get_u8()?;
let b = self.get_u8()?;
Ok((a as i16) << 8 | (b as i16))
}
fn get_u24(&mut self) -> Result<u32, ErrReadVideoReason> {
let a = self.get_u8()?;
let b = self.get_u8()?;
let c = self.get_u8()?;
Ok((a as u32) << 16 | (b as u32) << 8 | (c as u32))
}
fn get_u32(&mut self) -> Result<u32, ErrReadVideoReason> {
let a = self.get_u8()?;
let b = self.get_u8()?;
let c = self.get_u8()?;
let d = self.get_u8()?;
Ok((a as u32) << 24 | (b as u32) << 16 | (c as u32) << 8 | (d as u32))
}
fn get_u64(&mut self) -> Result<u64, ErrReadVideoReason> {
let a = self.get_u32()?;
let b = self.get_u32()?;
Ok((a as u64) << 32 | (b as u64))
}
fn get_char(&mut self) -> Result<char, ErrReadVideoReason> {
let a = self.get_u8()?;
Ok(a as char)
}
fn get_buffer<U>(&mut self, length: U) -> Result<Vec<u8>, ErrReadVideoReason>
where
U: Into<usize>,
{
let length = length.into();
let offset = *self.offset_mut();
*self.offset_mut() += length;
self.raw_data()
.get(offset..(offset + length))
.map(|vv| vv.to_vec())
.ok_or(ErrReadVideoReason::FileIsTooShort)
}
fn get_c_buffer(&mut self, end: char) -> Result<Vec<u8>, ErrReadVideoReason> {
let mut s = vec![];
loop {
let the_byte = self.get_char()?;
if the_byte == end {
break;
}
s.push(the_byte as u8);
}
Ok(s)
}
fn get_utf8_string<U>(&mut self, length: U) -> Result<String, ErrReadVideoReason>
where
U: Into<usize>,
{
let length = length.into();
String::from_utf8(self.get_buffer(length)?).map_err(|_e| ErrReadVideoReason::Utf8Error)
}
/// 读取以end结尾的合法utf-8字符串
fn get_utf8_c_string(&mut self, end: char) -> Result<String, ErrReadVideoReason> {
String::from_utf8(self.get_c_buffer(end)?).map_err(|_e| ErrReadVideoReason::Utf8Error)
}
fn get_unknown_encoding_string<U>(&mut self, length: U) -> Result<String, ErrReadVideoReason>
where
U: Into<usize>,
{
let code = self.get_buffer(length)?;
if let Ok(s) = String::from_utf8(code.clone()) {
return Ok(s);
}
let (cow, _, had_errors) = GB18030.decode(&code);
if !had_errors {
return Ok(cow.into_owned());
};
let (cow, _, had_errors) = WINDOWS_1252.decode(&code);
if !had_errors {
return Ok(cow.into_owned());
};
Ok(String::from_utf8_lossy(&code).to_string())
}
/// 读取以end结尾的未知编码字符串,假如所有编码都失败,返回utf-8乱码
fn get_unknown_encoding_c_string(&mut self, end: char) -> Result<String, ErrReadVideoReason> {
let code = self.get_c_buffer(end)?;
if let Ok(s) = String::from_utf8(code.clone()) {
return Ok(s);
}
let (cow, _, had_errors) = GB18030.decode(&code);
if !had_errors {
return Ok(cow.into_owned());
};
let (cow, _, had_errors) = WINDOWS_1252.decode(&code);
if !had_errors {
return Ok(cow.into_owned());
};
Ok(String::from_utf8_lossy(&code).to_string())
}
// 是否闰年,计算阿比特时间戳
fn is_leap_year(&self, year: u64) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
// 一个月有几天,计算阿比特时间戳
fn days_in_month(&self, year: u64, month: u64) -> u32 {
let days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if month == 2 && self.is_leap_year(year) {
29
} else {
days[(month - 1) as usize]
}
}
fn days_since_epoch(&self, year: u64, month: u64, day: u64) -> u64 {
let mut total_days = 0;
for y in 1970..year {
total_days += if self.is_leap_year(y) { 366 } else { 365 };
}
for m in 1..month {
total_days += self.days_in_month(year, m) as u64;
}
total_days + day as u64 - 1
}
/// 解析avf里的开始时间戳,返回时间戳,微秒。“6606”只取后三位“606”,三位数取后两位
/// "18.10.2022.20:15:35:6606" -> 1666124135606000
fn parse_avf_start_timestamp(
&mut self,
start_timestamp: &str,
) -> Result<u64, ErrReadVideoReason> {
let mut timestamp_parts = start_timestamp.split('.');
let day = timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let month = timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let year = timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
timestamp_parts = timestamp_parts.next().unwrap().split(':');
let hour = timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let minute = timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let second = timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let sub_second = timestamp_parts.next().unwrap()[1..]
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let days = self.days_since_epoch(year, month, day);
let total_seconds = days * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second;
let microseconds = total_seconds * 1_000_000 + sub_second * 1_000;
Ok(microseconds)
}
// 解析avf里的结束时间戳,返回时间戳,微秒
// "18.10.2022.20:15:35:6606", "18.20:16:24:8868" -> 1666124184868000
fn parse_avf_end_timestamp(
&mut self,
start_timestamp: &str,
end_timestamp: &str,
) -> Result<u64, ErrReadVideoReason> {
let mut start_timestamp_parts = start_timestamp.split('.');
let mut end_timestamp_parts = end_timestamp.split('.');
let start_day = start_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let end_day = end_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let mut month = start_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let mut year = start_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
if start_day > end_day {
// 跨月
month += 1;
if month >= 13 {
month = 1;
year += 1;
}
}
end_timestamp_parts = end_timestamp_parts.next().unwrap().split(':');
let hour = end_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let minute = end_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let second = end_timestamp_parts
.next()
.unwrap()
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let sub_second = end_timestamp_parts.next().unwrap()[1..]
.parse::<u64>()
.map_err(|_| ErrReadVideoReason::InvalidParams)?;
let days = self.days_since_epoch(year, month, end_day);
let total_seconds = days * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second;
let microseconds = total_seconds * 1_000_000 + sub_second * 1_000;
Ok(microseconds)
}
}
use crate::{MouseState, GameBoard};
use std::cell::RefCell;
use std::rc::Rc;
/// 读录像文件失败的原因
#[derive(Debug)]
pub enum ErrReadVideoReason {
CanNotFindFile,
FileIsTooShort,
FileIsNotRmv,
FileIsEmpty,
InvalidBoardSize,
InvalidLevel,
InvalidParams,
InvalidVideoEvent,
InvalidMinePosition,
VersionBackward,
Utf8Error,
}
#[derive(Clone)]
pub enum Event {
Mouse(MouseEvent),
GameState(GameStateEvent),
Board(BoardEvent),
Index(IndexEvent),
}
/// evf标准中的鼠标事件
#[derive(Clone)]
pub struct MouseEvent {
/// 操作类型,这几种:"mv", "lc", "lr", "rc", "rr", "mc", "mr", "pf", "cc", "l", "r", "m"
pub mouse: String,
/// 距离左端有几像素。
pub x: u16,
/// 距离上端有几像素。
pub y: u16,
}
/// evf标准中的游戏状态事件
#[derive(Clone)]
pub struct GameStateEvent {
/// 操作类型,这几种:{81: "replay", 82: "win", 83: "fail", 99: "error"}
pub game_state: String,
}
/// evf标准中的局面事件
#[derive(Clone)]
pub struct BoardEvent {
/// 操作类型,这几种:{100: "cell_0", 101: "cell_1", 102: "cell_2", 103: "cell_3", 104: "cell_4",
/// 105: "cell_5", 106: "cell_6", 107: "cell_7", 108: "cell_8", 110: "up", 111: "flag",
/// 114: "cross mine", 115: "blast", 116: "mine", 118: "pressed", 120: "questionmark",
/// 121: "pressed questionmark"}
pub board: String,
/// 从上往下,从0开始数,第几行
pub row_id: u8,
/// 从左往右,从0开始数,第几列
pub column_id: u8,
}
#[derive(Clone)]
pub enum IndexValue {
Number(f64),
String(String),
}
/// evf标准中的指标事件
#[derive(Clone)]
pub struct IndexEvent {
pub key: String,
pub value: IndexValue,
}
/// 录像里的局面活动(点击或移动)、指标状态(该活动完成后的)、先验后验局面索引
#[derive(Clone)]
pub struct VideoActionStateRecorder {
/// 相对时间,从0开始,大于rtime
pub time: f64,
pub event: Option<Event>,
/// 操作类型,这几种:"mv", "lc", "lr", "rc", "rr", "mc", "mr", "pf"
// pub mouse: String,
// /// 距离左端有几像素。
// pub x: u16,
// /// 距离上端有几像素。
// pub y: u16,
/// 0代表完全没用;
/// 1代表能仅推进局面但不改变对局面的后验判断,例如标雷和取消标雷;
/// 2代表改变对局面的后验判断的操作,例如左键点开一个或一片格子,不包括双击;
/// 3代表有效、至少打开了一个格子的双击;
/// 4代表踩雷并失败;
/// 和ce没有关系,仅用于控制计算
pub useful_level: u8,
/// 操作前的局面(先验局面)的计数引用。
pub prior_game_board: Option<Rc<RefCell<GameBoard>>>,
/// 操作后的局面(后验的局面)的计数引用。
pub next_game_board: Option<Rc<RefCell<GameBoard>>>,
pub comments: String,
/// 该操作完成以后的鼠标状态。和录像高亮有关。即使是鼠标move也会记录。
pub mouse_state: MouseState,
/// 该操作完成以后,已解决的3BV。
// pub solved3BV: usize,
/// 指标状态(该活动完成后的、后验的), mv也会记录,浪费了空间
pub key_dynamic_params: KeyDynamicParams,
pub path: f64,
}
impl Default for VideoActionStateRecorder {
fn default() -> Self {
VideoActionStateRecorder {
time: 0.0,
event: None,
useful_level: 0,
prior_game_board: None,
next_game_board: None,
comments: "".to_string(),
mouse_state: MouseState::Undefined,
key_dynamic_params: KeyDynamicParams::default(),
path: 0.0,
}
}
}
#[derive(Clone)]
pub struct StaticParams {
pub bbbv: usize,
pub op: usize,
pub isl: usize,
pub hizi: usize,
pub cell0: usize,
pub cell1: usize,
pub cell2: usize,
pub cell3: usize,
pub cell4: usize,
pub cell5: usize,
pub cell6: usize,
pub cell7: usize,
pub cell8: usize,
/// 鼠标回报率
pub fps: usize,
}
impl Default for StaticParams {
fn default() -> Self {
StaticParams {
bbbv: 0,
op: 0,
isl: 0,
hizi: 0,
cell0: 0,
cell1: 0,
cell2: 0,
cell3: 0,
cell4: 0,
cell5: 0,
cell6: 0,
cell7: 0,
cell8: 0,
fps: 0,
}
}
}
/// 侧重实时记录中间过程、中间状态
/// 每个鼠标事件都会存一个,mv也存,存在浪费
#[derive(Clone)]
pub struct KeyDynamicParams {
pub left: usize,
pub right: usize,
pub double: usize,
// ce = lce + rce + dce
pub lce: usize,
pub rce: usize,
pub dce: usize,
pub flag: usize,
pub bbbv_solved: usize,
pub op_solved: usize,
pub isl_solved: usize,
pub pluck: f64,
}
impl Default for KeyDynamicParams {
fn default() -> Self {
KeyDynamicParams {
left: 0,
right: 0,
double: 0,
lce: 0,
rce: 0,
dce: 0,
flag: 0,
bbbv_solved: 0,
op_solved: 0,
isl_solved: 0,
pluck: f64::NAN,
}
}
}
/// 游戏动态类指标,侧重保存最终结果
/// 游戏阶段就可以展示
#[derive(Clone)]
pub struct GameDynamicParams {
/// 最终时间成绩,不是时间的函数
pub rtime: f64,
/// 以毫秒为单位的精确时间
pub rtime_ms: u32,
pub left: usize,
pub right: usize,
pub double: usize,
pub cl: usize,
pub flag: usize,
pub left_s: f64,
pub right_s: f64,
pub double_s: f64,
pub cl_s: f64,
pub flag_s: f64,
/// 四舍五入折算到16像素边长,最终路径长度
pub path: f64,
}
impl Default for GameDynamicParams {
fn default() -> Self {
GameDynamicParams {
rtime: 0.0,
rtime_ms: 0,
left: 0,
right: 0,
double: 0,
cl: 0,
flag: 0,
left_s: 0.0,
right_s: 0.0,
double_s: 0.0,
cl_s: 0.0,
flag_s: 0.0,
path: 0.0,
}
}
}
/// 录像动态类指标,侧重保存最终结果
/// 游戏阶段不能展示,录像播放时可以展示
#[derive(Clone)]
pub struct VideoDynamicParams {
pub etime: f64,
pub bbbv_s: f64,
pub bbbv_solved: usize,
pub stnb: f64,
pub rqp: f64,
pub qg: f64,
pub lce: usize,
pub rce: usize,
pub dce: usize,
pub ce: usize,
pub ce_s: f64,
pub ioe: f64,
pub corr: f64,
pub thrp: f64,
// 未完成
pub op_solved: usize,
// 未完成
pub isl_solved: usize,
}
impl Default for VideoDynamicParams {
fn default() -> Self {
VideoDynamicParams {
etime: 0.0,
bbbv_s: 0.0,
bbbv_solved: 0,
stnb: 0.0,
rqp: 0.0,
qg: 0.0,
lce: 0,
rce: 0,
dce: 0,
ce: 0,
ce_s: 0.0,
ioe: 0.0,
corr: 0.0,
thrp: 0.0,
op_solved: 0,
isl_solved: 0,
}
}
}
/// 需要分析才能计算出的指标,通常计算代价很大。最终结果
/// 游戏阶段不能展示,录像播放时可以展示
#[derive(Clone)]
pub struct VideoAnalyseParams {
pub pluck: f64,
}
impl Default for VideoAnalyseParams {
fn default() -> Self {
VideoAnalyseParams { pluck: f64::NAN }
}
}
// 测试录像分析模块
use ms_toollib::videos::base_video::NewBaseVideo2;
use ms_toollib::{BaseVideo, Evfs, SafeBoard};
use std::time::Duration;
use std::{thread, vec};
fn _sleep_ms(ms: u32) {
thread::sleep(Duration::from_millis(ms as u64));
}
#[test]
fn evfs_save_works() {
let mut evfs = Evfs::new();
// 第1盘,成果
let board = vec![
vec![1, 1, 2, 1, 1, 0, 0, 0],
vec![1, -1, 2, -1, 1, 0, 0, 0],
vec![1, 1, 2, 1, 1, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
vec![2, 2, 1, 0, 0, 0, 0, 0],
vec![-1, -1, 2, 0, 0, 1, 1, 1],
vec![-1, -1, 3, 0, 0, 2, -1, 2],
vec![-1, -1, 2, 0, 0, 2, -1, 2],
];
let mut video = BaseVideo::<SafeBoard>::new(board, 16);
_sleep_ms(60);
video.step("rc", (17, 16)).unwrap();
video.step("rr", (17, 16)).unwrap();
video.step("rc", (16, 49)).unwrap();
_sleep_ms(20);
video.step("rr", (16, 50)).unwrap();
video.step("lc", (16, 32)).unwrap();
_sleep_ms(20);
video.step("lr", (16, 32)).unwrap();
_sleep_ms(20);
video.step("lc", (52, 0)).unwrap();
video.step("lr", (53, 0)).unwrap();
video.step("lc", (16, 32)).unwrap();
video.step("rc", (16, 32)).unwrap();
_sleep_ms(5);
video.step("rr", (16, 32)).unwrap();
_sleep_ms(5);
video.step("lr", (16, 32)).unwrap();
_sleep_ms(5);
video.step("lc", (0, 16)).unwrap();
_sleep_ms(5);
video.step("rc", (0, 16)).unwrap();
_sleep_ms(5);
video.step("rr", (0, 16)).unwrap();
_sleep_ms(5);
video.step("lr", (0, 16)).unwrap();
video.step("mv", (4800, 51)).unwrap();
video.step("lc", (112, 112)).unwrap();
video.step("lr", (112, 112)).unwrap();
video.step("lc", (97, 112)).unwrap();
video.step("lr", (97, 112)).unwrap();
video.set_player_identifier("eee555".to_string()).unwrap();
video.set_race_identifier("G8888".to_string()).unwrap();
video.set_software("a test software".to_string()).unwrap();
video.set_country("CN".to_string()).unwrap();
video.generate_evf_v4_raw_data();
let check_sum_evf = vec![8; 32];
video.set_checksum_evf_v4(check_sum_evf).unwrap();
let check_sum_cell = vec![9; 32];
evfs.append(video.get_raw_data().unwrap(), "test_1", check_sum_cell);
// 第2盘,失败
let board = vec![
vec![1, 1, 2, 1, 1],
vec![1, -1, 2, -1, 1],
vec![1, 1, 2, 1, 1],
vec![0, 0, 0, 0, 0],
vec![0, 0, 0, 0, 0],
];
let mut video = BaseVideo::<SafeBoard>::new(board, 20);
_sleep_ms(60);
// println!("3BV:{:?}", video.static_params.bbbv);
video.step("lc", (7, 7)).unwrap();
video.step("lr", (7, 7)).unwrap();
video.step("mv", (48, 51)).unwrap();
video.step("mv", (20, 77)).unwrap();
_sleep_ms(20);
video.step("lc", (20, 21)).unwrap();
video.step("lr", (21, 25)).unwrap();
video.generate_evf_v4_raw_data();
let check_sum_evf = vec![12; 32];
video.set_checksum_evf_v4(check_sum_evf).unwrap();
let check_sum_cell = vec![13; 32];
evfs.append(video.get_raw_data().unwrap(), "test_2", check_sum_cell);
// 第3盘,成果
let board = vec![
vec![0, 0, 2, -1, 2, 0, 0, 0],
vec![0, 0, 3, -1, 3, 0, 0, 0],
vec![0, 0, 2, -1, 2, 0, 0, 0],
vec![0, 0, 1, 1, 1, 1, 1, 1],
vec![0, 0, 0, 0, 0, 1, -1, -1],
vec![1, 1, 0, 0, 0, 1, 2, -1],
vec![-1, 3, 1, 0, 0, 0, 2, -1],
vec![-1, -1, 1, 0, 0, 0, 2, -1],
];
let mut video = BaseVideo::<SafeBoard>::new(board, 16);
_sleep_ms(60);
video.step("rc", (32, 49)).unwrap();
video.step("rr", (32, 49)).unwrap();
_sleep_ms(20);
video.step("lc", (48, 64)).unwrap();
_sleep_ms(20);
video.step("mv", (1, 51)).unwrap();
video.step("mv", (2, 51)).unwrap();
video.step("mv", (3, 51)).unwrap();
video.step("mv", (3, 4)).unwrap();
video.step("mv", (48, 5)).unwrap();
video.step("mv", (48, 6)).unwrap();
video.step("lr", (48, 64)).unwrap();
_sleep_ms(20);
video.step("lc", (48, 64)).unwrap();
_sleep_ms(20);
video.step("rc", (48, 64)).unwrap();
_sleep_ms(20);
video.step("lr", (48, 64)).unwrap();
_sleep_ms(20);
video.step("rr", (48, 64)).unwrap();
video.generate_evf_v4_raw_data();
let check_sum_cell = vec![15; 32];
evfs.append(video.get_raw_data().unwrap(), "test_3", check_sum_cell);
// 生成 EVFS V0 原始数据,保存到文件
evfs.generate_evfs_v0_raw_data();
evfs.save_evfs_file("test");
// 重新读取evfs文件,并测试解析
let mut evfs = Evfs::new_with_file("test.evfs");
evfs.parse().unwrap();
let cell1_3 = &evfs[0..3];
assert_eq!(cell1_3[0].evf_video.data.software, "a test software".to_string());
let cell2 = &evfs[1];
assert!(!cell2.evf_video.data.is_completed);
evfs.save_evf_files("./");
}
use ms_toollib_original::*;
use pyo3::prelude::*;
use pyo3::types::{PyInt, PyList, PySlice};
use crate::videos::EvfVideo;
#[pyclass(name = "EvfsCell", unsendable)]
pub struct PyEvfsCell {
pub core: EvfsCell,
}
#[pymethods]
impl PyEvfsCell {
#[getter]
pub fn get_evf_video(&self) -> EvfVideo {
EvfVideo {
core: self.core.evf_video.clone()
}
}
#[getter]
pub fn get_checksum(&self) -> PyResult<Vec<u8>> {
Ok(self.core.checksum.clone())
}
}
#[pyclass(name = "Evfs", unsendable)]
pub struct PyEvfs {
pub core: Evfs,
}
#[pymethods]
impl PyEvfs {
#[new]
#[pyo3(signature = (file_name="", raw_data=vec![]))]
pub fn new(file_name: &str, raw_data: Vec<u8>) -> Self {
if raw_data.is_empty() {
if file_name.is_empty() {
PyEvfs { core: Evfs::new() }
} else {
PyEvfs {
core: Evfs::new_with_file(file_name),
}
}
} else {
PyEvfs {
core: Evfs::new_with_data(raw_data),
}
}
}
pub fn append(&mut self, data: Vec<u8>, file_name: &str, checksum: Vec<u8>) {
self.core.append(data, file_name, checksum);
}
pub fn pop(&mut self) {
self.core.pop();
}
pub fn len(&self) -> usize {
self.core.len()
}
pub fn is_empty(&self) -> bool {
self.core.is_empty()
}
pub fn clear(&mut self) {
self.core.clear();
}
/// 初步验证evfs文件的有效性。适用于网页前端,并不严格。
pub fn is_valid(&mut self) -> bool {
self.core.is_valid()
}
pub fn get_software(&self) -> &str {
&self.core.get_software()
}
pub fn get_evf_version(&self) -> u8 {
self.core.get_evf_version()
}
pub fn get_start_time(&self) -> u64 {
self.core.get_start_time()
}
pub fn get_end_time(&self) -> u64 {
self.core.get_end_time()
}
/// 生成evfs_v0文件的二进制数据
pub fn generate_evfs_v0_raw_data(&mut self) {
self.core.generate_evfs_v0_raw_data();
}
pub fn parse(&mut self) {
self.core.parse().unwrap();
}
pub fn save_evf_files(&self, dir: &str) {
self.core.save_evf_files(dir);
}
pub fn save_evfs_file(&self, file_name: &str) -> PyResult<String> {
self.core.save_evfs_file(file_name);
Ok(file_name.to_string())
}
pub fn __getitem__(&self, py: Python<'_>, key: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
// 先尝试当作整数索引(支持负索引)
if let Ok(index_obj) = key.cast::<PyInt>() {
let idx: isize = index_obj.extract()?;
let adjusted_idx = if idx < 0 {
(self.core.len() as isize + idx) as usize
} else {
idx as usize
};
if adjusted_idx < self.core.len() {
let cell = PyEvfsCell {
core: self.core[adjusted_idx].clone(),
};
// 把 pyclass 包装成 PyObject 返回
return Ok(Py::new(py, cell)?.into());
} else {
return Err(pyo3::exceptions::PyIndexError::new_err(
"Index out of range",
));
}
}
// 再尝试当作 slice
if let Ok(slice) = key.cast::<PySlice>() {
// 使用 slice.indices 来把切片规范化为 (start, stop, step)
let length = self.core.len() as isize;
let indices = slice.indices(length)?;
let mut result: Vec<Py<PyAny>> = Vec::with_capacity(indices.slicelength);
let mut i = indices.start;
let stop = indices.stop;
let step = indices.step;
if step > 0 {
while i < stop {
let cell = PyEvfsCell {
core: self.core[i as usize].clone(),
};
result.push(Py::new(py, cell)?.into());
i += step;
}
} else {
while i > stop {
let cell = PyEvfsCell {
core: self.core[i as usize].clone(),
};
result.push(Py::new(py, cell)?.into());
i += step; // step is negative here
}
}
return Ok(PyList::new(py, result)?.into());
}
// 不是 int 也不是 slice,报类型错误
Err(pyo3::exceptions::PyTypeError::new_err(
"Invalid key type, expected int or slice",
))
}
}
+5
-1

@@ -62,4 +62,8 @@ [package]

[profile.release]
opt-level = "z" # 优化大小
lto = true # 链接时优化,跨crate优化代码
panic = "abort" # 禁用恐慌的展开信息
strip = true # 剥离调试符号

@@ -542,3 +542,3 @@ use crate::utils::{

) -> f64 {
let mut poss = 0.0;
let mut poss = 1.0;
let mut game_board_modified = game_board.clone();

@@ -1637,1 +1637,48 @@ for &(x, y) in cells.iter() {

}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cal_probability_cells_not_mine() {
let game_board = vec![
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 2, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
];
let p = cal_probability_cells_not_mine(
&game_board,
10.0,
&vec![(2, 2), (3, 2), (4, 2), (2, 4), (3, 4), (4, 4)],
);
println!("{:?}", p);
}
#[test]
fn test_cal_probability_cells_not_mine_2() {
let game_board = vec![
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 1, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
vec![10, 10, 10, 10, 10, 10, 10, 10],
];
let p = cal_probability_cells_not_mine(
&game_board,
11.0,
&vec![(2, 3), (2, 4), (2, 5), (3, 3), (3, 5), (4, 3), (4, 4), (4, 5)],
);
println!("{:?}", p);
}
}

@@ -99,6 +99,11 @@ //! # 扫雷算法工具箱

mod evfs;
pub use evfs::{Evfs, EvfsCell};
pub mod videos;
pub use videos::{
valid_time_period, AvfVideo, BaseVideo, EvfVideo, GameBoardState, MinesweeperBoard, MouseState,
MvfVideo, RmvVideo,
MvfVideo, RmvVideo,BoardEvent, ErrReadVideoReason, Event, GameDynamicParams, GameStateEvent, IndexEvent,
IndexValue, KeyDynamicParams, MouseEvent, VideoActionStateRecorder, VideoAnalyseParams,
VideoDynamicParams
};

@@ -105,0 +110,0 @@

+0
-49
#[cfg(any(feature = "py", feature = "rs"))]
use rand::Rng;
// pub fn laymine_safely(
// row: usize,
// column: usize,
// mine_num: usize,
// x0: usize,
// y0: usize,
// ) -> SafeBoard {
// let board = laymine(row, column, mine_num, x0, y0);
// SafeBoard::new(board)
// }
#[cfg(any(feature = "py", feature = "rs"))]

@@ -137,26 +126,2 @@ fn encode(v: i32, rng: &mut rand::rngs::ThreadRng) -> (i32, i32, i32) {

// impl std::ops::IndexMut<usize> for SafeBoardRow {
// fn index_mut(&mut self, index: usize) -> &mut Self::Output {
// let t = decode(
// self.value_1[index],
// self.value_2[index],
// self.value_3[index],
// ) as usize;
// let t = self.table[t];
// let (a, b, c) = encode(t, &mut self.rng);
// self.value_1[index] = a;
// self.value_2[index] = b;
// self.value_3[index] = c;
// &mut self.value[index]
// }
// }
// impl<'a> IntoIterator for &'a SafeBoardRow {
// type Item = i32;
// type IntoIter = std::vec::IntoIter<Self::Item>;
// fn into_iter(self) -> Self::IntoIter {
// self.value_1.clone().into_iter().map()
// }
// }
#[cfg(any(feature = "py", feature = "rs"))]

@@ -209,16 +174,2 @@ impl Iterator for SafeBoardRow {

// impl std::ops::IndexMut<usize> for SafeBoard {
// fn index_mut(&mut self, index: usize) -> &mut Self::Output {
// &mut self.value[index]
// }
// }
// impl<'a> IntoIterator for &'a SafeBoard {
// type Item = SafeBoardRow;
// type IntoIter = std::vec::IntoIter<Self::Item>;
// fn into_iter(self) -> Self::IntoIter {
// self.value.clone().into_iter()
// }
// }
pub trait BoardSize {

@@ -225,0 +176,0 @@ fn get_row(&self) -> usize;

use crate::algorithms::{cal_probability_cells_not_mine, mark_board};
use crate::utils::is_good_chording;
use crate::videos::base_video::BaseVideo;
use crate::videos::types::Event;
use crate::MouseState;

@@ -22,20 +23,23 @@ use std::cmp::{max, min};

pub fn analyse_high_risk_guess(video: &mut BaseVideo<Vec<Vec<i32>>>) {
let mut x;
let mut y;
let mut r;
let mut c;
for ide in 2..video.video_action_state_recorder.len() {
x = (video.video_action_state_recorder[ide].y / video.cell_pixel_size as u16) as usize;
y = (video.video_action_state_recorder[ide].x / video.cell_pixel_size as u16) as usize;
if video.video_action_state_recorder[ide].useful_level >= 2 {
let p = video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_poss()[x][y];
if p >= 0.51 {
video.video_action_state_recorder[ide].comments = format!(
"{}{}",
video.video_action_state_recorder[ide].comments,
format!("error: 危险的猜雷(猜对概率{:.3});", 1.0 - p)
);
let vas = &mut video.video_action_state_recorder[ide];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
r = (mouse_event.y / video.cell_pixel_size as u16) as usize;
c = (mouse_event.x / video.cell_pixel_size as u16) as usize;
if vas.useful_level >= 2 {
let p = vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_poss()[r][c];
if p >= 0.51 {
vas.comments = format!(
"{}{}",
vas.comments,
format!("error: 危险的猜雷(猜对概率{:.3});", 1.0 - p)
);
}
}

@@ -48,18 +52,11 @@ }

// 功能:检测左键或右键的跳判
let mut x;
let mut y;
let mut r;
let mut c;
for ide in 2..video.video_action_state_recorder.len() {
x = (video.video_action_state_recorder[ide].y / video.cell_pixel_size as u16) as usize;
y = (video.video_action_state_recorder[ide].x / video.cell_pixel_size as u16) as usize;
if video.video_action_state_recorder[ide].useful_level >= 2
&& video.video_action_state_recorder[ide].mouse == "lr"
{
if !video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_basic_not_mine()
.contains(&(x, y))
&& video.video_action_state_recorder[ide]
let vas = &mut video.video_action_state_recorder[ide];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
r = (mouse_event.y / video.cell_pixel_size as u16) as usize;
c = (mouse_event.x / video.cell_pixel_size as u16) as usize;
if vas.useful_level >= 2 && mouse_event.mouse == "lr" {
if !vas
.prior_game_board

@@ -69,22 +66,20 @@ .as_ref()

.borrow_mut()
.get_enum_not_mine()
.contains(&(x, y))
{
video.video_action_state_recorder[ide].comments = format!(
"{}{}",
video.video_action_state_recorder[ide].comments,
format!("feature: 高难度的判雷(左键);")
);
}
} else if video.video_action_state_recorder[ide].useful_level == 1
&& video.video_action_state_recorder[ide].mouse == "rc"
{
if !video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_basic_is_mine()
.contains(&(x, y))
&& video.video_action_state_recorder[ide]
.get_basic_not_mine()
.contains(&(r, c))
&& vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_enum_not_mine()
.contains(&(r, c))
{
vas.comments = format!(
"{}{}",
vas.comments,
format!("feature: 高难度的判雷(左键);")
);
}
} else if vas.useful_level == 1 && mouse_event.mouse == "rc" {
if !vas
.prior_game_board

@@ -94,10 +89,18 @@ .as_ref()

.borrow_mut()
.get_enum_is_mine()
.contains(&(x, y))
{
video.video_action_state_recorder[ide].comments = format!(
"{}{}",
video.video_action_state_recorder[ide].comments,
format!("feature: 高难度的判雷(标雷);")
);
.get_basic_is_mine()
.contains(&(r, c))
&& vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_enum_is_mine()
.contains(&(r, c))
{
vas.comments = format!(
"{}{}",
vas.comments,
format!("feature: 高难度的判雷(标雷);")
);
}
}

@@ -110,29 +113,22 @@ }

pub fn analyse_needless_guess(video: &mut BaseVideo<Vec<Vec<i32>>>) {
let mut x;
let mut y;
let mut r;
let mut c;
'outer: for ide in 2..video.video_action_state_recorder.len() {
if video.video_action_state_recorder[ide].useful_level >= 2
&& video.video_action_state_recorder[ide].mouse == "lr"
{
x = (video.video_action_state_recorder[ide].y / video.cell_pixel_size as u16) as usize;
y = (video.video_action_state_recorder[ide].x / video.cell_pixel_size as u16) as usize;
let vas = &mut video.video_action_state_recorder[ide];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
if vas.useful_level >= 2 && mouse_event.mouse == "lr" {
r = (mouse_event.y / video.cell_pixel_size as u16) as usize;
c = (mouse_event.x / video.cell_pixel_size as u16) as usize;
if video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_poss()[x][y]
> 0.0
{
for m in max(2, x) - 2..min(video.height, x + 3) {
for n in max(2, y) - 2..min(video.width, y + 3) {
if video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_basic_not_mine()
.contains(&(m, n))
|| video.video_action_state_recorder[ide]
if vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_poss()[r][c]
> 0.0
{
for m in max(2, r) - 2..min(video.height, r + 3) {
for n in max(2, c) - 2..min(video.width, c + 3) {
if vas
.prior_game_board

@@ -142,11 +138,19 @@ .as_ref()

.borrow_mut()
.get_enum_not_mine()
.get_basic_not_mine()
.contains(&(m, n))
{
video.video_action_state_recorder[ide].comments = format!(
"{}{}",
video.video_action_state_recorder[ide].comments,
format!("warning: 可以判雷时选择猜雷;")
);
continue 'outer;
|| vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_enum_not_mine()
.contains(&(m, n))
{
vas.comments = format!(
"{}{}",
vas.comments,
format!("warning: 可以判雷时选择猜雷;")
);
continue 'outer;
}
}

@@ -160,67 +164,59 @@ }

/// 检查鼠标轨迹是否弯曲
pub fn analyse_mouse_trace(video: &mut BaseVideo<Vec<Vec<i32>>>) {
let mut click_last = (
video.video_action_state_recorder[0].x as f64,
video.video_action_state_recorder[0].y as f64,
);
let Some(Event::Mouse(mut last_click_event)) =
video.video_action_state_recorder[0].event.clone()
else {
panic!("expected mouse event");
};
let Some(Event::Mouse(mut last_move_event)) =
video.video_action_state_recorder[0].event.clone()
else {
panic!("expected mouse event");
};
let mut comments = vec![];
let mut click_last_id = 0;
let mut move_last = (
video.video_action_state_recorder[0].x as f64,
video.video_action_state_recorder[0].y as f64,
);
let mut path = 0.0;
for ide in 0..video.video_action_state_recorder.len() {
let current_x = video.video_action_state_recorder[ide].x as f64;
let current_y = video.video_action_state_recorder[ide].y as f64;
path += ((move_last.0 - current_x).powf(2.0) + (move_last.1 - current_y).powf(2.0)).sqrt();
move_last = (current_x, current_y);
if video.video_action_state_recorder[ide].mouse == "lr"
|| video.video_action_state_recorder[ide].mouse == "rc"
|| video.video_action_state_recorder[ide].mouse == "rr"
{
let path_straight = ((click_last.0 - current_x).powf(2.0)
+ (click_last.1 - current_y).powf(2.0))
let vas = &mut video.video_action_state_recorder[ide];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
let current_x = mouse_event.x as f64;
let current_y = mouse_event.y as f64;
path += ((last_move_event.x as f64 - current_x).powf(2.0)
+ (last_move_event.y as f64 - current_y).powf(2.0))
.sqrt();
let k = path / path_straight;
if k > 7.0 {
video.video_action_state_recorder[click_last_id].comments = format!(
"{}{}",
video.video_action_state_recorder[click_last_id].comments,
format!("error: 过于弯曲的鼠标轨迹({:.0}%);", k * 100.0)
);
// println!(
// "{:?} => {:?}",
// video.video_action_state_recorder[click_last_id].time, video.video_action_state_recorder[click_last_id].comments
// );
} else if k > 3.5 {
video.video_action_state_recorder[click_last_id].comments = format!(
"{}{}",
video.video_action_state_recorder[click_last_id].comments,
format!("warning: 弯曲的鼠标轨迹({:.0}%);", k * 100.0)
);
// println!(
// "{:?} => {:?}",
// video.video_action_state_recorder[click_last_id].time, video.video_action_state_recorder[click_last_id].comments
// );
} else if k < 1.01 {
if k > 5.0 {
video.video_action_state_recorder[click_last_id].comments = format!(
"{}{}",
video.video_action_state_recorder[click_last_id].comments,
format!("suspect: 笔直的鼠标轨迹;")
);
// println!(
// "{:?} => {:?}",
// video.video_action_state_recorder[click_last_id].time, video.video_action_state_recorder[click_last_id].comments
// );
last_move_event = mouse_event.clone();
if mouse_event.mouse == "lr" || mouse_event.mouse == "rc" || mouse_event.mouse == "rr" {
let path_straight = ((last_click_event.x as f64 - current_x).powf(2.0)
+ (last_click_event.y as f64 - current_y).powf(2.0))
.sqrt();
let k = path / path_straight;
if k > 7.0 {
comments.push((
click_last_id,
format!("error: 过于弯曲的鼠标轨迹({:.0}%);", k * 100.0),
));
} else if k > 3.5 {
comments.push((
click_last_id,
format!("warning: 弯曲的鼠标轨迹({:.0}%);", k * 100.0),
));
} else if k < 1.01 {
if k > 5.0 {
comments.push((click_last_id, "suspect: 笔直的鼠标轨迹;".to_owned()));
}
}
last_click_event = mouse_event.clone();
click_last_id = ide;
path = 0.0;
}
click_last = (
video.video_action_state_recorder[ide].x as f64,
video.video_action_state_recorder[ide].y as f64,
);
click_last_id = ide;
path = 0.0;
}
}
for comment in comments {
video.video_action_state_recorder[comment.0].comments = video.video_action_state_recorder
[comment.0]
.comments
.clone()
+ &comment.1;
}
}

@@ -230,86 +226,106 @@

pub fn analyse_vision_transfer(video: &mut BaseVideo<Vec<Vec<i32>>>) {
let mut click_last = (
video.video_action_state_recorder[0].y as f64,
video.video_action_state_recorder[0].x as f64,
);
let mut l_x = (video.video_action_state_recorder[0].y / video.cell_pixel_size as u16) as usize;
let mut l_y = (video.video_action_state_recorder[0].x / video.cell_pixel_size as u16) as usize;
let Some(Event::Mouse(mut last_click_event)) =
video.video_action_state_recorder[0].event.clone()
else {
panic!("expected mouse event");
};
let mut comments = vec![];
let mut last_c = (last_click_event.y / video.cell_pixel_size as u16) as usize;
let mut last_r = (last_click_event.x / video.cell_pixel_size as u16) as usize;
let mut click_last_id = 0;
for ide in 0..video.video_action_state_recorder.len() {
if video.video_action_state_recorder[ide].useful_level >= 2 {
// let xx = (video.video_action_state_recorder[ide].y / video.cell_pixel_size) as usize;
// let yy = (video.video_action_state_recorder[ide].x / video.cell_pixel_size) as usize;
let click_current = (
video.video_action_state_recorder[ide].y as f64,
video.video_action_state_recorder[ide].x as f64,
);
if ((click_last.0 - click_current.0).powf(2.0)
+ (click_last.1 - click_current.1).powf(2.0))
.sqrt()
/ video.cell_pixel_size as f64
>= 6.0
{
let mut flag = false;
for &(xxx, yyy) in video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_basic_not_mine()
let vas = &mut video.video_action_state_recorder[ide];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
if vas.useful_level >= 2 {
if ((last_click_event.x as f64 - mouse_event.x as f64).powf(2.0)
+ (last_click_event.y as f64 - mouse_event.y as f64).powf(2.0))
.sqrt()
/ video.cell_pixel_size as f64
>= 6.0
{
if xxx <= l_x + 3 && xxx + 3 >= l_x && yyy <= l_y + 3 && yyy + 3 >= l_y {
flag = true;
let mut flag = false;
for &(xxx, yyy) in vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_basic_not_mine()
{
if xxx <= last_c + 3
&& xxx + 3 >= last_c
&& yyy <= last_r + 3
&& yyy + 3 >= last_r
{
flag = true;
}
}
}
for &(xxx, yyy) in video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_enum_not_mine()
{
if xxx <= l_x + 3 && xxx + 3 >= l_x && yyy <= l_y + 3 && yyy + 3 >= l_y {
flag = true;
for &(xxx, yyy) in vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_enum_not_mine()
{
if xxx <= last_c + 3
&& xxx + 3 >= last_c
&& yyy <= last_r + 3
&& yyy + 3 >= last_r
{
flag = true;
}
}
if flag {
comments.push((click_last_id, "warning: 可以判雷时视野的转移;"));
}
}
if flag {
video.video_action_state_recorder[click_last_id].comments = format!(
"{}{}",
video.video_action_state_recorder[click_last_id].comments,
format!("warning: 可以判雷时视野的转移;")
);
// println!(
// "{:?} => {:?}",
// video.video_action_state_recorder[click_last_id].time, video.video_action_state_recorder[click_last_id].comments
// );
}
last_click_event = mouse_event.clone();
last_c = (last_click_event.y / video.cell_pixel_size as u16) as usize;
last_r = (last_click_event.x / video.cell_pixel_size as u16) as usize;
click_last_id = ide;
}
click_last = click_current;
l_x =
(video.video_action_state_recorder[ide].y / video.cell_pixel_size as u16) as usize;
l_y =
(video.video_action_state_recorder[ide].x / video.cell_pixel_size as u16) as usize;
click_last_id = ide;
}
}
for comment in comments {
video.video_action_state_recorder[comment.0].comments = video.video_action_state_recorder
[comment.0]
.comments
.clone()
+ &comment.1;
}
}
/// 计算扫开这局的后验开率
/// 不计算comment,修改pluck参数
pub fn analyse_survive_poss(video: &mut BaseVideo<Vec<Vec<i32>>>) {
/// 计算回放的录像的各个时刻的pluck参数
pub fn analyse_pluck(video: &mut BaseVideo<Vec<Vec<i32>>>) {
let mut pluck = 0.0;
let mut has_begin = false;
for vas in video.video_action_state_recorder.iter_mut() {
if vas.useful_level == 2 {
// 有效的左键
if !has_begin {
has_begin = true;
vas.key_dynamic_params.pluck = Some(0.0);
continue;
}
let x = (vas.y / video.cell_pixel_size as u16) as usize;
let y = (vas.x / video.cell_pixel_size as u16) as usize;
// 安全的概率
let p = 1.0
- vas
if let Some(Event::Mouse(mouse_event)) = &vas.event {
if vas.useful_level == 2 {
// 有效的左键
if !has_begin {
has_begin = true;
vas.key_dynamic_params.pluck = 0.0;
continue;
}
let r = (mouse_event.y / video.cell_pixel_size as u16) as usize;
let c = (mouse_event.x / video.cell_pixel_size as u16) as usize;
// 安全的概率
let p = 1.0
- vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.get_poss()[r][c];
if p <= 0.0 || pluck == f64::MAX {
pluck = f64::MAX;
} else if p < 1.0 {
pluck -= p.log10();
}
} else if vas.useful_level == 3 {
// 有效的双键
let r = (mouse_event.y / video.cell_pixel_size as u16) as usize;
let c = (mouse_event.x / video.cell_pixel_size as u16) as usize;
let mut game_board_clone = vas
.prior_game_board

@@ -319,48 +335,36 @@ .as_ref()

.borrow_mut()
.get_poss()[x][y];
if p <= 0.0 || pluck == f64::MAX {
pluck = f64::MAX;
} else if p < 1.0 {
pluck -= p.log10();
}
} else if vas.useful_level == 3 {
// 有效的双键
let x = (vas.y / video.cell_pixel_size as u16) as usize;
let y = (vas.x / video.cell_pixel_size as u16) as usize;
let mut game_board_clone = vas
.prior_game_board
.as_ref()
.unwrap()
.borrow_mut()
.game_board
.clone();
let _ = mark_board(&mut game_board_clone, true).unwrap();
let mut chording_cells = vec![];
for m in max(1, x) - 1..min(video.height, x + 2) {
for n in max(1, y) - 1..min(video.width, y + 2) {
if game_board_clone[m][n] == 10 {
chording_cells.push((m, n));
.game_board
.clone();
let mut chording_cells = vec![];
for m in max(1, r) - 1..min(video.height, r + 2) {
for n in max(1, c) - 1..min(video.width, c + 2) {
if game_board_clone[m][n] == 10 {
chording_cells.push((m, n));
}
}
}
}
// 安全的概率
let p = cal_probability_cells_not_mine(
&game_board_clone,
video.mine_num as f64,
&chording_cells,
);
if p >= 1.0 || pluck == f64::MAX {
let _ = mark_board(&mut game_board_clone, true).unwrap();
// 安全的概率
let p = cal_probability_cells_not_mine(
&game_board_clone,
video.mine_num as f64,
&chording_cells,
);
if p <= 0.0 || pluck == f64::MAX {
pluck = f64::MAX;
} else if p > 0.0 {
pluck -= p.log10();
}
} else if vas.useful_level == 4 {
pluck = f64::MAX;
} else if p > 0.0 {
pluck -= p.log10();
}
}
if has_begin {
vas.key_dynamic_params.pluck = Some(pluck);
} else {
vas.key_dynamic_params.pluck = Some(0.0);
if has_begin {
vas.key_dynamic_params.pluck = pluck;
} else {
vas.key_dynamic_params.pluck = 0.0;
}
}
}
video.video_analyse_params.pluck = Some(pluck);
video.video_analyse_params.pluck = pluck;
}

@@ -377,2 +381,3 @@

pub fn analyse_super_fl_local(video: &mut BaseVideo<Vec<Vec<i32>>>) {
let mut comments = vec![];
let event_min_num = 5;

@@ -384,59 +389,85 @@ let euclidean_distance = 16;

let mut last_rc_num = 0; // 最后有几个右键
let mut last_ide = 0;
// let mut last_ide = 0;
let Some(Event::Mouse(mut last_event)) = video.video_action_state_recorder[0].event.clone()
else {
panic!("expected mouse event");
};
let mut last_event_mouse_state = video.video_action_state_recorder[0].mouse_state;
for ide in 1..video.video_action_state_recorder.len() {
if video.video_action_state_recorder[ide].mouse == "mv" {
continue;
}
let x = video.video_action_state_recorder[ide].y as usize / video.cell_pixel_size as usize;
let y = video.video_action_state_recorder[ide].x as usize / video.cell_pixel_size as usize;
let x_1 =
video.video_action_state_recorder[last_ide].y as usize / video.cell_pixel_size as usize;
let y_1 =
video.video_action_state_recorder[last_ide].x as usize / video.cell_pixel_size as usize;
// if video.video_action_state_recorder[ide].mouse == "lr" || video.video_action_state_recorder[ide].mouse == "rr"{
// println!("{:?}+++{:?}", video.video_action_state_recorder[last_ide].time, video.video_action_state_recorder[last_ide].mouse_state);
// // println!("---{:?}", video.video_action_state_recorder[ide].useful_level);
// }
let vas = &mut video.video_action_state_recorder[ide];
if let Some(Event::Mouse(mouse_event)) = &vas.event {
if mouse_event.mouse == "mv" {
continue;
}
let x = mouse_event.y as usize / video.cell_pixel_size as usize;
let y = mouse_event.x as usize / video.cell_pixel_size as usize;
let r_1 = last_event.y as usize / video.cell_pixel_size as usize;
let c_1 = last_event.x as usize / video.cell_pixel_size as usize;
// if video.video_action_state_recorder[ide].mouse == "lr" || video.video_action_state_recorder[ide].mouse == "rr"{
// println!("{:?}+++{:?}", video.video_action_state_recorder[last_ide].time, video.video_action_state_recorder[last_ide].mouse_state);
// // println!("---{:?}", video.video_action_state_recorder[ide].useful_level);
// }
if video.video_action_state_recorder[ide].mouse == "rc"
&& video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow()
.game_board[x][y]
== 10
&& video.video_action_state_recorder[ide].useful_level == 1
{
// 正确的标雷
match state {
SuperFLState::NotStart => {
state = SuperFLState::StartNow;
counter = 1;
last_rc_num = 1;
anchor = ide;
// println!("666");
if mouse_event.mouse == "rc"
&& vas.prior_game_board.as_ref().unwrap().borrow().game_board[x][y] == 10
&& vas.useful_level == 1
{
// 正确的标雷
match state {
SuperFLState::NotStart => {
state = SuperFLState::StartNow;
counter = 1;
last_rc_num = 1;
anchor = ide;
// println!("666");
}
SuperFLState::StartNow => {
state = SuperFLState::StartNotOk;
counter += 1;
last_rc_num += 1;
}
SuperFLState::StartNotOk | SuperFLState::IsOk => {
counter += 1;
last_rc_num += 1;
}
_ => {}
}
SuperFLState::StartNow => {
state = SuperFLState::StartNotOk;
counter += 1;
last_rc_num += 1;
} else if vas.useful_level == 3 {
// 正确的双击
if !is_good_chording(
&vas.prior_game_board.as_ref().unwrap().borrow().game_board,
(x, y),
) {
match state {
SuperFLState::IsOk => {
counter -= last_rc_num;
state = SuperFLState::Finish;
}
_ => {
state = SuperFLState::NotStart;
counter = 0;
last_rc_num = 0;
}
}
} else {
match state {
SuperFLState::StartNow => {
state = SuperFLState::StartNotOk;
counter += 1;
last_rc_num = 0;
}
SuperFLState::StartNotOk | SuperFLState::IsOk => {
counter += 1;
last_rc_num = 0;
}
_ => {}
}
}
SuperFLState::StartNotOk | SuperFLState::IsOk => {
counter += 1;
last_rc_num += 1;
}
_ => {}
}
} else if video.video_action_state_recorder[ide].useful_level == 3 {
// 正确的双击
if !is_good_chording(
&video.video_action_state_recorder[ide]
.prior_game_board
.as_ref()
.unwrap()
.borrow()
.game_board,
(x, y),
) {
} else if mouse_event.mouse == "lr"
&& (last_event_mouse_state == MouseState::DownUp
|| last_event_mouse_state == MouseState::Chording)
|| mouse_event.mouse == "rr" && last_event_mouse_state == MouseState::Chording
{
// 左键或错误的右键或错误的双键
match state {

@@ -453,12 +484,16 @@ SuperFLState::IsOk => {

}
} else {
}
if (x as i32 - r_1 as i32) * (x as i32 - r_1 as i32)
+ (y as i32 - c_1 as i32) * (y as i32 - c_1 as i32)
> euclidean_distance
{
match state {
SuperFLState::StartNow => {
state = SuperFLState::StartNotOk;
counter += 1;
SuperFLState::StartNotOk => {
state = SuperFLState::NotStart;
counter = 0;
last_rc_num = 0;
}
SuperFLState::StartNotOk | SuperFLState::IsOk => {
counter += 1;
last_rc_num = 0;
SuperFLState::IsOk => {
counter -= last_rc_num;
state = SuperFLState::Finish;
}

@@ -468,60 +503,38 @@ _ => {}

}
} else if video.video_action_state_recorder[ide].mouse == "lr"
&& (video.video_action_state_recorder[last_ide].mouse_state == MouseState::DownUp
|| video.video_action_state_recorder[last_ide].mouse_state == MouseState::Chording)
|| video.video_action_state_recorder[ide].mouse == "rr"
&& video.video_action_state_recorder[last_ide].mouse_state == MouseState::Chording
{
// 左键或错误的右键或错误的双键
match state {
SuperFLState::IsOk => {
counter -= last_rc_num;
state = SuperFLState::Finish;
if counter - last_rc_num >= event_min_num {
match state {
SuperFLState::StartNow | SuperFLState::StartNotOk => {
state = SuperFLState::IsOk;
}
_ => {}
}
_ => {
state = SuperFLState::NotStart;
counter = 0;
last_rc_num = 0;
}
}
}
if (x as i32 - x_1 as i32) * (x as i32 - x_1 as i32)
+ (y as i32 - y_1 as i32) * (y as i32 - y_1 as i32)
> euclidean_distance
{
match state {
SuperFLState::StartNotOk => {
SuperFLState::Finish => {
comments.push((
anchor,
format!("feature: 教科书式的FL局部(步数{});", counter),
));
// video.video_action_state_recorder[anchor].comments = format!(
// "{}{}",
// video.video_action_state_recorder[anchor].comments,
// format!("feature: 教科书式的FL局部(步数{});", counter)
// );
state = SuperFLState::NotStart;
counter = 0;
last_rc_num = 0;
}
SuperFLState::IsOk => {
counter -= last_rc_num;
state = SuperFLState::Finish;
}
_ => {}
}
last_event = mouse_event.clone();
last_event_mouse_state = vas.mouse_state;
// println!("{:?}", video.video_action_state_recorder[last_ide].mouse_state);
}
if counter - last_rc_num >= event_min_num {
match state {
SuperFLState::StartNow | SuperFLState::StartNotOk => {
state = SuperFLState::IsOk;
}
_ => {}
}
}
match state {
SuperFLState::Finish => {
video.video_action_state_recorder[anchor].comments = format!(
"{}{}",
video.video_action_state_recorder[anchor].comments,
format!("feature: 教科书式的FL局部(步数{});", counter)
);
state = SuperFLState::NotStart;
}
_ => {}
}
last_ide = ide;
// println!("{:?}", video.video_action_state_recorder[last_ide].mouse_state);
}
for comment in comments {
video.video_action_state_recorder[comment.0].comments = video.video_action_state_recorder
[comment.0]
.comments
.clone()
+ &comment.1;
}
}

@@ -1,6 +0,5 @@

// use crate::MouseState;
// use crate::miscellaneous::s_to_ms;
// use crate::utils::cal_board_numbers;
use crate::videos::base_video::NewBaseVideo;
use crate::videos::base_video::{BaseVideo, ErrReadVideoReason, VideoActionStateRecorder};
use crate::videos::base_video::{BaseVideo, NewBaseVideo};
use crate::videos::byte_reader::ByteReader;
use crate::videos::types::{ErrReadVideoReason, Event, MouseEvent, VideoActionStateRecorder};
#[cfg(any(feature = "py", feature = "rs"))]

@@ -256,6 +255,20 @@ use crate::videos::NewSomeVideo;

self.data.start_time = self.data.parse_avf_start_timestamp(&start_time)?;
// 时间戳部分正常情况举例:
// 初级:[0|26.10.2022.23:13:27:2236|26.23:13:29:7764|B7T3.52]
// 高级破纪录时:[2|18.10.2022.20:15:35:6606|18.20:16:24:8868|HS|B127T50.25]
// 自定义:[3|W8H11M7|3.9.2025.17:00:08:6660|3.17:00:14:081|B8T6.42]
// 异常情况举例:
// 高级:[2|17.7.2012.12:08:03:3338|17.12:09:44:6697B248T102.34]
let mut end_time = String::new();
let mut buffer: [char; 2];
loop {
match self.data.get_char()? {
'|' => break,
'|' => {
buffer = ['\0', '|'];
break;
}
'B' => {
buffer = ['|', 'B'];
break;
}
other => end_time.push(other),

@@ -265,9 +278,3 @@ }

self.data.end_time = self.data.parse_avf_end_timestamp(&start_time, &end_time)?;
let mut buffer: [char; 2];
match self.data.get_char()? {
'|' => buffer = ['\0', '|'],
'B' => buffer = ['|', 'B'],
_ => buffer = ['\0', '\0'],
}
// 此处以下10行的写法有危险
loop {

@@ -306,18 +313,22 @@ if buffer[0] == '|' && buffer[1] == 'B' {

+ (buffer[4] as f64) / 100.0,
mouse: match buffer[0] {
1 => "mv".to_string(),
3 => "lc".to_string(),
5 => "lr".to_string(),
9 => "rc".to_string(),
17 => "rr".to_string(),
33 => "mc".to_string(),
65 => "mr".to_string(),
145 => "rr".to_string(),
193 => "mr".to_string(),
11 => "sc".to_string(), // left_click_with_shift没见过,不清楚用途
21 => "lr".to_string(),
_ => return Err(ErrReadVideoReason::InvalidVideoEvent),
},
x: (buffer[1] as u16) << 8 | buffer[3] as u16,
y: (buffer[5] as u16) << 8 | buffer[7] as u16,
event: Some(Event::Mouse(MouseEvent {
mouse: match buffer[0] {
1 => "mv".to_string(),
3 => "lc".to_string(),
5 => "lr".to_string(),
9 => "rc".to_string(),
17 => "rr".to_string(),
33 => "mc".to_string(),
65 => "mr".to_string(),
145 => "rr".to_string(),
193 => "mr".to_string(),
11 => panic!(),
// left_click_with_shift没见过,不清楚用途
// 11 => "sc".to_string(),
21 => "lr".to_string(),
_ => return Err(ErrReadVideoReason::InvalidVideoEvent),
},
x: (buffer[1] as u16) << 8 | buffer[3] as u16,
y: (buffer[5] as u16) << 8 | buffer[7] as u16,
})),
..VideoActionStateRecorder::default()

@@ -324,0 +335,0 @@ });

@@ -0,5 +1,7 @@

use crate::GameStateEvent;
use crate::miscellaneous::s_to_ms;
use crate::utils::cal_board_numbers;
use crate::videos::base_video::NewBaseVideo;
use crate::videos::base_video::{BaseVideo, ErrReadVideoReason, VideoActionStateRecorder};
use crate::videos::base_video::{BaseVideo, NewBaseVideo};
use crate::videos::byte_reader::ByteReader;
use crate::videos::types::{ErrReadVideoReason, Event, MouseEvent, VideoActionStateRecorder};
#[cfg(any(feature = "py", feature = "rs"))]

@@ -54,4 +56,6 @@ use crate::videos::NewSomeVideo;

/// ```
#[derive(Clone)]
pub struct EvfVideo {
pub file_name: String,
pub version: u8,
pub data: BaseVideo<Vec<Vec<i32>>>,

@@ -62,6 +66,9 @@ }

impl NewSomeVideo<&str> for EvfVideo {
/// 从文件名创建EvfVideo实例
fn new(file_name: &str) -> Self {
let data = BaseVideo::<Vec<Vec<i32>>>::new(file_name);
EvfVideo {
file_name: file_name.to_string(),
data: BaseVideo::<Vec<Vec<i32>>>::new(file_name),
version: data.get_raw_data().unwrap()[0],
data,
}

@@ -72,5 +79,7 @@ }

impl NewSomeVideo2<Vec<u8>, &str> for EvfVideo {
/// 从二进制数据和虚拟的文件名创建EvfVideo实例
fn new(raw_data: Vec<u8>, file_name: &str) -> Self {
EvfVideo {
file_name: file_name.to_string(),
version: raw_data[0],
data: BaseVideo::<Vec<Vec<i32>>>::new(raw_data),

@@ -196,5 +205,7 @@ }

time,
mouse: mouse.to_string(),
x,
y,
event: Some(Event::Mouse(MouseEvent {
mouse: mouse.to_string(),
x,
y,
})),
..VideoActionStateRecorder::default()

@@ -318,5 +329,7 @@ });

time,
mouse: mouse.to_string(),
x,
y,
event: Some(Event::Mouse(MouseEvent {
mouse: mouse.to_string(),
x,
y,
})),
..VideoActionStateRecorder::default()

@@ -447,5 +460,7 @@ });

time,
mouse: mouse.to_string(),
x,
y,
event: Some(Event::Mouse(MouseEvent {
mouse: mouse.to_string(),
x,
y,
})),
..VideoActionStateRecorder::default()

@@ -535,17 +550,19 @@ });

2 => mouse = "lc",
3 => mouse = "lr",
4 => mouse = "rc",
5 => mouse = "rr",
6 => mouse = "mc",
7 => mouse = "mr",
8 => mouse = "pf",
9 => mouse = "cc",
10 => mouse = "l",
11 => mouse = "r",
12 => mouse = "m",
_ => mouse = "ub", // impossible
_ => panic!(), // impossible
}
let time = self.data.get_u8()? as f64 / 1000.0;
let time_ms = self.data.get_u8()? as u32;
let time = time_ms as f64 / 1000.0;
let x = self.data.get_u16()?;
let y = self.data.get_u16()?;
let event_0 = MouseEvent {
mouse: mouse.to_string(),
x,
y,
};
// 增量计算鼠标坐标用,只有鼠标事件才有坐标
let mut last_mouse_event = event_0.clone();
// 增量时间戳用,所有事件都有时间戳
let mut last_event_time_ms = time_ms;
self.data

@@ -555,11 +572,12 @@ .video_action_state_recorder

time,
mouse: mouse.to_string(),
x,
y,
event: Some(Event::Mouse(event_0)),
..VideoActionStateRecorder::default()
});
// 累计的暂停时间
let mut pause_time_ms = 0;
// for i in 0..200{
// print!("{:?}, ", self.data.get_u8()?);
// }
loop {
let byte = self.data.get_u8()?;
let mouse;
match byte {

@@ -569,14 +587,71 @@ 0 => {

}
1 => mouse = "mv",
2 => mouse = "lc",
3 => mouse = "lr",
4 => mouse = "rc",
5 => mouse = "rr",
6 => mouse = "mc",
7 => mouse = "mr",
8 => mouse = "pf",
9 => mouse = "cc",
10 => mouse = "l",
11 => mouse = "r",
12 => mouse = "m",
// 开始解析鼠标事件
byte_mouse @ 1..=80 => {
let mouse;
match byte_mouse {
1 => mouse = "mv",
2 => mouse = "lc",
3 => mouse = "lr",
4 => mouse = "rc",
5 => mouse = "rr",
6 => mouse = "mc",
7 => mouse = "mr",
8 => mouse = "pf",
9 => mouse = "cc",
10 => mouse = "l",
11 => mouse = "r",
12 => mouse = "m",
_ => panic!(),
}
let time_u8: u8 = self.data.get_u8()?;
let x = self.data.get_i16()?;
let y = self.data.get_i16()?;
// let last_event = self.data.video_action_state_recorder.last().unwrap();
let event_i = MouseEvent {
mouse: mouse.to_string(),
x: (last_mouse_event.x as i16 + x) as u16,
y: (last_mouse_event.y as i16 + y) as u16,
};
let time_i_ms = time_u8 as u32 + pause_time_ms + last_event_time_ms;
let time_i = time_i_ms as f64 / 1000.0;
last_mouse_event = event_i.clone();
last_event_time_ms = time_i_ms;
self.data
.video_action_state_recorder
.push(VideoActionStateRecorder {
time: time_i,
event: Some(Event::Mouse(event_i)),
..VideoActionStateRecorder::default()
});
pause_time_ms = 0;
}
// 开始解析游戏状态事件
byte_game_state @ 81..=99 => {
let game_state;
match byte_game_state {
81 => game_state = "replay",
82 => game_state = "win",
83 => game_state = "fail",
99 => game_state = "error",
_ => panic!(),
}
let time_u8: u8 = self.data.get_u8()?;
let event_i = GameStateEvent {
game_state: game_state.to_string(),
};
let time_i_ms = time_u8 as u32 + pause_time_ms + last_event_time_ms;
let time_i = time_i_ms as f64 / 1000.0;
last_event_time_ms = time_i_ms;
self.data
.video_action_state_recorder
.push(VideoActionStateRecorder {
time: time_i,
event: Some(Event::GameState(event_i)),
..VideoActionStateRecorder::default()
});
pause_time_ms = 0;
}
b @ 100..=199 => {}
b @ 200..=254 => {}
// 开始解析停顿事件
255 => {

@@ -587,20 +662,3 @@ let pause_time = self.data.get_u16()?;

}
_ => {
continue;
}
}
let time: u8 = self.data.get_u8()?;
let x = self.data.get_i16()?;
let y = self.data.get_i16()?;
let last_event = self.data.video_action_state_recorder.last().unwrap();
self.data
.video_action_state_recorder
.push(VideoActionStateRecorder {
time: (s_to_ms(last_event.time) + time as u32 + pause_time_ms) as f64 / 1000.0,
mouse: mouse.to_string(),
x: (last_event.x as i16 + x) as u16,
y: (last_event.y as i16 + y) as u16,
..VideoActionStateRecorder::default()
});
pause_time_ms = 0;
}

@@ -607,0 +665,0 @@

@@ -37,2 +37,3 @@ use crate::utils::refresh_board;

/// ```
#[derive(Clone)]
pub struct MinesweeperBoard<T> {

@@ -451,6 +452,3 @@ pub board: T,

"pf" => {
assert!(
self.game_board[pos.0][pos.1] == 10,
"按定义,pf不能在标雷上执行。请报告这个奇怪的录像。"
);
assert!(self.game_board[pos.0][pos.1] == 10, "");
self.pre_flag_num += 1;

@@ -561,6 +559,3 @@ self.game_board_state = GameBoardState::PreFlaging;

"pf" => {
assert!(
self.game_board[pos.0][pos.1] == 10,
"按定义,pf不能在标雷上执行。请报告这个奇怪的录像。"
);
assert!(self.game_board[pos.0][pos.1] == 10, "");
self.pre_flag_num += 1;

@@ -859,2 +854,16 @@ return self.right_click(pos.0, pos.1);

}
/// 实施游戏状态事件
pub fn step_game_state(&mut self, e: &str) -> Result<u8, ()>
where
T: std::ops::Index<usize> + BoardSize + std::fmt::Debug,
T::Output: std::ops::Index<usize, Output = i32>,
{
match e {
"replay" | "fail" => {
self.game_board_state = GameBoardState::Loss;
Ok(0)
}
_ => Err(()),
}
}
fn is_win(&mut self) -> bool

@@ -861,0 +870,0 @@ where

@@ -1,16 +0,24 @@

pub mod avf_video;
pub use avf_video::{AvfVideo};
pub mod rmv_video;
pub use rmv_video::{RmvVideo};
pub mod evf_video;
pub use evf_video::{EvfVideo};
pub mod mvf_video;
pub use mvf_video::{MvfVideo};
pub mod base_video;
pub use base_video::{BaseVideo, valid_time_period};
pub mod minesweeper_board;
pub use minesweeper_board::{MinesweeperBoard, GameBoardState, MouseState};
pub mod types;
pub use types::{
BoardEvent, ErrReadVideoReason, Event, GameDynamicParams, GameStateEvent, IndexEvent,
IndexValue, KeyDynamicParams, MouseEvent, VideoActionStateRecorder, VideoAnalyseParams,
VideoDynamicParams,
};
pub mod avf_video;
pub use avf_video::AvfVideo;
pub mod rmv_video;
pub use rmv_video::RmvVideo;
pub mod evf_video;
pub use evf_video::EvfVideo;
pub mod mvf_video;
pub use mvf_video::MvfVideo;
pub mod base_video;
pub use base_video::{valid_time_period, BaseVideo};
pub mod base_video_metrics;
pub mod base_video_generate_evf;
pub mod byte_reader;
pub mod minesweeper_board;
pub use minesweeper_board::{GameBoardState, MinesweeperBoard, MouseState};
mod analyse_methods;
pub trait NewSomeVideo<T> {

@@ -23,3 +31,1 @@ fn new(file_name: T) -> Self;

}
use crate::utils::cal_board_numbers;
use crate::videos::base_video::NewBaseVideo;
use crate::videos::base_video::{BaseVideo, ErrReadVideoReason, VideoActionStateRecorder};
use crate::videos::base_video::{BaseVideo, NewBaseVideo};
use crate::videos::byte_reader::ByteReader;
use crate::videos::types::{ErrReadVideoReason, Event, MouseEvent, VideoActionStateRecorder};
#[cfg(any(feature = "py", feature = "rs"))]

@@ -287,5 +288,3 @@ use crate::videos::NewSomeVideo;

time: ths as f64 / 1000.0 + sec as f64,
mouse,
x,
y,
event: Some(Event::Mouse(MouseEvent { mouse, x, y })),
..VideoActionStateRecorder::default()

@@ -331,5 +330,3 @@ });

time: ths as f64 / 1000.0 + sec as f64,
mouse,
x,
y,
event: Some(Event::Mouse(MouseEvent { mouse, x, y })),
..VideoActionStateRecorder::default()

@@ -341,6 +338,8 @@ });

// 计算rtime。如前所述,录像记录的时间是从lc开始的,并不正确
for v in &self.data.video_action_state_recorder{
if v.mouse == "lr"{
start_t = v.time;
break;
for v in &self.data.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &v.event {
if mouse_event.mouse == "lr" {
start_t = v.time;
break;
}
}

@@ -347,0 +346,0 @@ }

use crate::utils::cal_board_numbers;
use crate::videos::base_video::NewBaseVideo;
use crate::videos::base_video::{BaseVideo, ErrReadVideoReason, VideoActionStateRecorder};
use crate::videos::base_video::{BaseVideo, NewBaseVideo};
use crate::videos::byte_reader::ByteReader;
use crate::videos::types::{ErrReadVideoReason, Event, MouseEvent, VideoActionStateRecorder};
#[cfg(any(feature = "py", feature = "rs"))]

@@ -198,5 +199,7 @@ use crate::videos::NewSomeVideo;

.push(VideoActionStateRecorder {
mouse: "pf".to_string(),
x: d * 16,
y: c * 16,
event: Some(Event::Mouse(MouseEvent {
mouse: "pf".to_string(),
x: d * 16,
y: c * 16,
})),
..VideoActionStateRecorder::default()

@@ -239,5 +242,7 @@ });

time: time as f64 / 1000.0,
mouse: "lc".to_string(),
x,
y,
event: Some(Event::Mouse(MouseEvent {
mouse: "lc".to_string(),
x,
y,
})),
..VideoActionStateRecorder::default()

@@ -250,14 +255,16 @@ });

time: time as f64 / 1000.0,
mouse: match c {
1 => "mv".to_string(),
2 => "lc".to_string(),
3 => "lr".to_string(),
4 => "rc".to_string(),
5 => "rr".to_string(),
6 => "mc".to_string(),
7 => "mr".to_string(),
_ => return Err(ErrReadVideoReason::InvalidVideoEvent),
},
x,
y,
event: Some(Event::Mouse(MouseEvent {
mouse: match c {
1 => "mv".to_string(),
2 => "lc".to_string(),
3 => "lr".to_string(),
4 => "rc".to_string(),
5 => "rr".to_string(),
6 => "mc".to_string(),
7 => "mr".to_string(),
_ => return Err(ErrReadVideoReason::InvalidVideoEvent),
},
x,
y,
})),
..VideoActionStateRecorder::default()

@@ -264,0 +271,0 @@ });

@@ -5,3 +5,3 @@ // 测试录像分析模块

use ms_toollib::{
AvfVideo, BaseVideo, EvfVideo, GameBoardState, MinesweeperBoard, MouseState, MvfVideo,
AvfVideo, BaseVideo, Event, EvfVideo, GameBoardState, MinesweeperBoard, MouseState, MvfVideo,
RmvVideo, SafeBoard,

@@ -32,10 +32,26 @@ };

let mut my_board = MinesweeperBoard::<Vec<Vec<i32>>>::new(board.clone());
my_board.step_flow(&vec![("rc".to_string(), (4, 1))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (4, 1))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (5, 1))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (5, 1))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (4, 1))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (4, 1))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (4, 1))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (4, 1))]).unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (4, 1))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (4, 1))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (5, 1))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (5, 1))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (4, 1))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (4, 1))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (4, 1))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (4, 1))])
.unwrap();
// my_board.board.iter().for_each(|x| println!("{:?}", x));

@@ -70,3 +86,3 @@ my_board.game_board.iter().for_each(|x| println!("{:?}", x));

assert_eq!(r.unwrap(), ());
video.data.print_event();
// video.data.print_event();
video.data.analyse();

@@ -156,3 +172,3 @@ assert!(video.data.player_identifier == "Wang Jianing G01825".to_string());

"jump_judge",
"survive_poss",
"pluck",
]);

@@ -247,2 +263,15 @@ // video.data.print_comments();

assert_eq!(video.data.get_stnb().unwrap(), 79.47351397906152);
video.data.analyse_for_features(vec![
"needless_guess",
"high_risk_guess",
"jump_judge",
"pluck",
]);
video.data.set_current_time(-0.01);
let t = video.data.get_game_board_poss();
println!("{:?}", video.data.get_game_board_poss());
video.data.set_current_time(20.0);
assert_eq!(video.data.get_pluck().unwrap(), 0.20115579693141436);
video.data.set_current_time(999.999);
assert_eq!(video.data.get_pluck().unwrap(), 0.3772470559870956);
}

@@ -332,6 +361,4 @@

assert_eq!(video.data.get_stnb().unwrap(), 104.33431983657493);
video.data.analyse_for_features(vec![
"survive_poss",
]);
assert_eq!(video.data.get_pluck().unwrap(), 0.9504906677386042);
video.data.analyse_for_features(vec!["pluck"]);
assert_eq!(video.data.get_pluck().unwrap(), 0.4612441009087633);
// video.data.print_comments();

@@ -385,3 +412,3 @@ }

// video.analyse_for_features(vec!["super_fl_local", "mouse_trace"]);
// video.data.analyse_for_features(vec!["jump_judge", "survive_poss"]);
// video.data.analyse_for_features(vec!["jump_judge", "pluck"]);
// video.data.print_comments();

@@ -426,7 +453,7 @@ }

video.step("rc", (16, 32)).unwrap();
_sleep_ms(5000);
_sleep_ms(20);
video.step("rr", (16, 32)).unwrap();
_sleep_ms(50);
_sleep_ms(7);
video.step("lr", (16, 32)).unwrap();
_sleep_ms(50);
_sleep_ms(13);
video.step("lc", (0, 16)).unwrap();

@@ -437,3 +464,3 @@ _sleep_ms(50);

video.step("rr", (0, 16)).unwrap();
assert!(video.get_left_s() <= 9.0);
assert!(video.get_left_s() <= 9000.0);
_sleep_ms(50);

@@ -466,2 +493,3 @@ video.step("lr", (0, 16)).unwrap();

assert!(video.get_rtime().unwrap() > 0.2);
assert_eq!(video.is_completed, true);

@@ -512,5 +540,131 @@

assert_eq!(video.data.get_checksum().unwrap(), vec![8; 32]);
for t in video.data.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &t.event {
println!(
"{:?}, {:?}, {:?}, {:?}",
mouse_event.mouse, t.time, mouse_event.x, mouse_event.y
);
}
}
}
#[test]
// cargo test --features rs -- --nocapture evf_video_works_v3
fn evf_video_works_v4_2() {
// 录像解析工具测试
let mut video = EvfVideo::new("../test_files/temp.evf");
let _ = video.parse_video();
// video.data.print_event();
video.data.analyse();
video.data.analyse_for_features(vec![
"high_risk_guess",
"jump_judge",
"needless_guess",
"mouse_trace",
"vision_transfer",
]);
assert_eq!(
video.data.player_identifier,
"[lag]二问题无法 玩家( player)"
);
assert_eq!(video.data.software, "元3.2.1");
println!("is win: {:?}", video.data.is_completed);
println!("is_official: {:?}", video.data.is_official);
println!("is_fair: {:?}", video.data.is_fair);
println!("is_valid: {:?}", video.data.is_valid());
for t in video.data.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &t.event {
println!(
"{:?}, {:?}, {:?}, {:?}",
mouse_event.mouse, t.time, mouse_event.x, mouse_event.y
);
}
}
}
#[test]
// cargo test --features rs -- --nocapture evf_video_works_v4
fn evf_video_works_v4_replay() {
// 录像解析工具测试
let board = vec![
vec![1, 1, 2, 1, 1, 0, 0, 0],
vec![1, -1, 2, -1, 1, 0, 0, 0],
vec![1, 1, 2, 1, 1, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
vec![2, 2, 1, 0, 0, 0, 0, 0],
vec![-1, -1, 2, 0, 0, 1, 1, 1],
vec![-1, -1, 3, 0, 0, 2, -1, 2],
vec![-1, -1, 2, 0, 0, 2, -1, 2],
];
let mut video = BaseVideo::<SafeBoard>::new(board, 16);
_sleep_ms(100);
// println!("3BV:{:?}", video.static_params.bbbv);
assert_eq!(video.game_board_state, GameBoardState::Ready);
video.step("rc", (17, 16)).unwrap();
assert_eq!(video.game_board_state, GameBoardState::PreFlaging);
video.step("rr", (17, 16)).unwrap();
video.step("rc", (16, 49)).unwrap();
_sleep_ms(20);
video.step("rr", (16, 50)).unwrap();
video.step("mv", (48, 51)).unwrap();
video.step("mv", (42, 48)).unwrap();
_sleep_ms(20);
video.step("lc", (16, 32)).unwrap();
_sleep_ms(20);
video.step("lr", (16, 32)).unwrap();
assert_eq!(video.game_board_state, GameBoardState::Playing);
_sleep_ms(20);
video.step("lc", (52, 0)).unwrap();
video.step("lr", (53, 0)).unwrap();
video.step("lc", (16, 32)).unwrap();
video.step("rc", (16, 32)).unwrap();
_sleep_ms(20);
video.step("rr", (16, 32)).unwrap();
_sleep_ms(7);
video.step("lr", (16, 32)).unwrap();
_sleep_ms(13);
video.step("lc", (0, 16)).unwrap();
_sleep_ms(50);
video.step("rc", (0, 16)).unwrap();
_sleep_ms(50);
video.step("rr", (0, 16)).unwrap();
video.step_game_state("replay").unwrap();
video
.set_player_identifier("English中文çкий языкにご한어ü".to_string())
.unwrap();
video.set_race_identifier("G8888".to_string()).unwrap();
video
.set_uniqueness_identifier("💣🚩1️⃣3️⃣8️⃣".to_string())
.unwrap();
video.set_software("a test software".to_string()).unwrap();
video.set_country("CN".to_string()).unwrap();
// video.print_event();
assert_eq!(video.game_board_state, GameBoardState::Loss);
assert_eq!(video.is_completed, false);
video.generate_evf_v4_raw_data();
video.set_checksum_evf_v4(vec![8; 32]).unwrap();
let test_file_name = video.save_to_evf_file("test");
let mut video = EvfVideo::new(&test_file_name);
let r = video.parse_video();
assert_eq!(r.unwrap(), ());
video.data.analyse();
assert!(!video.data.is_completed);
for t in video.data.video_action_state_recorder {
if let Some(Event::Mouse(mouse_event)) = &t.event {
println!(
"{:?}, {:?}, {:?}, {:?}",
mouse_event.mouse, t.time, mouse_event.x, mouse_event.y
);
}
}
}
#[test]
fn base_video_works() {

@@ -591,2 +745,3 @@ let board = vec![

println!("cell0: {:?}", video.static_params.cell0);
println!("pluck:{:?}", video.get_pluck());

@@ -629,3 +784,3 @@ video.generate_evf_v0_raw_data();

#[test]
fn base_video_works_2() {
fn base_video_works_2_win() {
let board = vec![

@@ -643,114 +798,338 @@ vec![0, 0, 0, 0, 1, 1, 1, 0],

let mut my_board = MinesweeperBoard::<Vec<Vec<i32>>>::new(board);
my_board.step_flow(&vec![("lc".to_string(), (2, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (2, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (1, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (3, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (3, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (4, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (2, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (3, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (4, 4))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (4, 3))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (4, 3))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (3, 5))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 5))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (3, 4))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (4, 4))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (4, 4))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (4, 4))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (4, 4))]).unwrap();
my_board.step_flow(&vec![("rc".to_string(), (1, 5))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (1, 5))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (2, 5))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (2, 5))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (2, 5))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (2, 5))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (2, 5))]).unwrap();
my_board.step_flow(&vec![("cc".to_string(), (2, 6))]).unwrap();
my_board.step_flow(&vec![("rr".to_string(), (2, 6))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (2, 6))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (0, 5))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 5))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (8, 8))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (8, 8))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (0, 7))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (0, 7))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (7, 3))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (7, 3))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (6, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (6, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (7, 2))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (7, 2))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (5, 0))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (5, 0))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (7, 1))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (7, 1))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (6, 1))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (6, 1))]).unwrap();
my_board.step_flow(&vec![("lc".to_string(), (7, 0))]).unwrap();
my_board.step_flow(&vec![("lr".to_string(), (7, 0))]).unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (2, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (2, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (1, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (3, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (3, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (4, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (2, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (3, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (4, 4))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (4, 3))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (4, 3))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (3, 5))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 5))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (3, 4))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (4, 4))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (4, 4))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (4, 4))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (4, 4))])
.unwrap();
my_board
.step_flow(&vec![("rc".to_string(), (1, 5))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (1, 5))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (2, 5))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (2, 5))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (2, 5))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (2, 5))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (2, 5))])
.unwrap();
my_board
.step_flow(&vec![("cc".to_string(), (2, 6))])
.unwrap();
my_board
.step_flow(&vec![("rr".to_string(), (2, 6))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (2, 6))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (0, 5))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 5))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (8, 8))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (8, 8))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (0, 7))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (0, 7))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (7, 3))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (7, 3))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (6, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (6, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (7, 2))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (7, 2))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (5, 0))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (5, 0))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (7, 1))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (7, 1))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (6, 1))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (6, 1))])
.unwrap();
my_board
.step_flow(&vec![("lc".to_string(), (7, 0))])
.unwrap();
my_board
.step_flow(&vec![("lr".to_string(), (7, 0))])
.unwrap();

@@ -982,3 +1361,3 @@ println!("game_board_state:{:?}", my_board.game_board_state);

#[test]
fn base_video_works_4() {
fn base_video_works_4_win() {
let board = vec![

@@ -995,3 +1374,3 @@ vec![0, 0, 2, -1, 2, 0, 0, 0],

let mut video = BaseVideo::<SafeBoard>::new(board, 16);
_sleep_ms(600);
_sleep_ms(60);
// println!("3BV:{:?}", video.static_params.bbbv);

@@ -1041,2 +1420,3 @@ video.step("rc", (32, 49)).unwrap();

println!("cell0: {:?}", video.static_params.cell0);
println!("pluck: {:?}", video.get_pluck());

@@ -1114,2 +1494,46 @@ video.generate_evf_v0_raw_data();

#[test]
fn base_video_works_6_fail() {
let board = vec![
vec![1, 1, 2, 1, 1, 0, 0, 0],
vec![1, -1, 2, -1, 1, 0, 0, 0],
vec![1, 1, 2, 1, 1, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
vec![2, 2, 1, 0, 0, 0, 0, 0],
vec![-1, -1, 2, 0, 0, 1, 1, 1],
vec![-1, -1, 3, 0, 0, 2, -1, 2],
vec![-1, -1, 2, 0, 0, 2, -1, 2],
];
let mut video = BaseVideo::<SafeBoard>::new(board, 16);
_sleep_ms(60);
video.step("lc", (17, 16)).unwrap();
video.step("lr", (17, 16)).unwrap();
assert_eq!(video.game_board_state, GameBoardState::Loss);
println!("pluck:{:?}", video.get_pluck());
}
#[test]
fn base_video_works_7_guess() {
let board = vec![
vec![-1, 2, 1, 0, 0, 0, 0, 0],
vec![2, -1, 2, 1, 0, 0, 0, 0],
vec![1, 2, -1, 1, 0, 0, 0, 0],
vec![0, 1, 1, 1, 0, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
vec![0, 0, 0, 0, 0, 0, 0, 0],
];
let mut video = BaseVideo::<SafeBoard>::new(board, 16);
_sleep_ms(60);
video.step("lc", (64, 64)).unwrap();
video.step("lr", (64, 64)).unwrap();
video.step("lc", (16, 0)).unwrap();
video.step("lr", (16, 0)).unwrap();
video.step("lc", (0, 16)).unwrap();
video.step("lr", (0, 16)).unwrap();
assert_eq!(video.game_board_state, GameBoardState::Win);
println!("pluck:{:?}", video.get_pluck());
}
#[test]
fn base_video_works_set_board() {

@@ -1253,7 +1677,7 @@ let board = vec![

#[test]
fn custom_video_works() {
// 自定义模式录像的测试
let mut video = AvfVideo::new("../test_files/Cus_8x11_7mines_5.42_3BV=8_3BVs=1.47_Wang Jianing G15208.avf");
let mut video =
AvfVideo::new("../test_files/Cus_8x11_7mines_5.42_3BV=8_3BVs=1.47_Wang Jianing G15208.avf");
let r = video.parse_video();

@@ -1278,7 +1702,5 @@ assert!(r.is_ok());

// video.analyse_for_features(vec!["super_fl_local", "mouse_trace"]);
// video.data.analyse_for_features(vec!["jump_judge", "survive_poss"]);
// video.data.analyse_for_features(vec!["jump_judge", "pluck"]);
// video.data.print_comments();
// video.data.is_valid();
}
Metadata-Version: 2.4
Name: ms_toollib
Version: 1.4.19
Version: 1.5.0
Summary: Algorithms for minesweeper.

@@ -5,0 +5,0 @@ Keywords: minesweeper,sweeper,probability,solver,3BV

[project]
name = "ms_toollib"
version = "1.4.19"
version = "1.5.0"
description = "Algorithms for minesweeper."

@@ -5,0 +5,0 @@ readme = "readme.md"

@@ -25,5 +25,5 @@ # This file is automatically @generated by Cargo.

name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [

@@ -35,5 +35,5 @@ "memchr",

name = "anyhow"
version = "1.0.99"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"

@@ -75,5 +75,5 @@ [[package]]

name = "bitflags"
version = "2.9.4"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"

@@ -103,5 +103,5 @@ [[package]]

name = "cc"
version = "1.2.35"
version = "1.2.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3"
checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe"
dependencies = [

@@ -114,5 +114,5 @@ "find-msvc-tools",

name = "cfg-if"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"

@@ -155,5 +155,5 @@ [[package]]

name = "deranged"
version = "0.5.3"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [

@@ -186,5 +186,5 @@ "powerfmt",

name = "doc-comment"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9"

@@ -226,8 +226,8 @@ [[package]]

name = "errno"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]

@@ -244,3 +244,3 @@

"libredox",
"windows-sys",
"windows-sys 0.60.2",
]

@@ -250,11 +250,11 @@

name = "find-msvc-tools"
version = "0.1.0"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "flate2"
version = "1.1.2"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [

@@ -267,5 +267,5 @@ "crc32fast",

name = "generic-array"
version = "0.14.7"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [

@@ -315,5 +315,8 @@ "typenum",

name = "indoc"
version = "2.0.6"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]

@@ -357,2 +360,8 @@ [[package]]

[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "kstring"

@@ -375,5 +384,5 @@ version = "2.0.2"

name = "libc"
version = "0.2.175"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"

@@ -388,5 +397,5 @@ [[package]]

name = "libredox"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [

@@ -400,5 +409,5 @@ "bitflags",

name = "linux-raw-sys"
version = "0.9.4"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"

@@ -444,3 +453,3 @@ [[package]]

"quote",
"syn 2.0.106",
"syn 2.0.109",
]

@@ -465,7 +474,6 @@

name = "lock_api"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"autocfg",
"scopeguard",

@@ -476,5 +484,5 @@ ]

name = "log"
version = "0.4.27"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"

@@ -499,11 +507,11 @@ [[package]]

name = "memchr"
version = "2.7.5"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memmap2"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7"
checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
dependencies = [

@@ -535,2 +543,3 @@ "libc",

"adler2",
"simd-adler32",
]

@@ -624,5 +633,5 @@

name = "parking_lot"
version = "0.12.4"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [

@@ -635,5 +644,5 @@ "lock_api",

name = "parking_lot_core"
version = "0.9.11"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [

@@ -644,3 +653,3 @@ "cfg-if",

"smallvec",
"windows-targets 0.52.6",
"windows-link",
]

@@ -662,8 +671,7 @@

name = "pest"
version = "2.8.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",

@@ -674,5 +682,5 @@ ]

name = "pest_derive"
version = "2.8.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
dependencies = [

@@ -685,5 +693,5 @@ "pest",

name = "pest_generator"
version = "2.8.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
dependencies = [

@@ -694,3 +702,3 @@ "pest",

"quote",
"syn 2.0.106",
"syn 2.0.109",
]

@@ -700,5 +708,5 @@

name = "pest_meta"
version = "2.8.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
dependencies = [

@@ -750,5 +758,5 @@ "pest",

name = "proc-macro2"
version = "1.0.101"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [

@@ -783,5 +791,5 @@ "unicode-ident",

name = "pyo3"
version = "0.25.1"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
checksum = "37a6df7eab65fc7bee654a421404947e10a0f7085b6951bf2ea395f4659fb0cf"
dependencies = [

@@ -801,7 +809,6 @@ "indoc",

name = "pyo3-build-config"
version = "0.25.1"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
checksum = "f77d387774f6f6eec64a004eac0ed525aab7fa1966d94b42f743797b3e395afb"
dependencies = [
"once_cell",
"target-lexicon",

@@ -812,5 +819,5 @@ ]

name = "pyo3-ffi"
version = "0.25.1"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
checksum = "2dd13844a4242793e02df3e2ec093f540d948299a6a77ea9ce7afd8623f542be"
dependencies = [

@@ -823,5 +830,5 @@ "libc",

name = "pyo3-macros"
version = "0.25.1"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
checksum = "eaf8f9f1108270b90d3676b8679586385430e5c0bb78bb5f043f95499c821a71"
dependencies = [

@@ -831,3 +838,3 @@ "proc-macro2",

"quote",
"syn 2.0.106",
"syn 2.0.109",
]

@@ -837,5 +844,5 @@

name = "pyo3-macros-backend"
version = "0.25.1"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
checksum = "70a3b2274450ba5288bc9b8c1b69ff569d1d61189d4bff38f8d22e03d17f932b"
dependencies = [

@@ -846,3 +853,3 @@ "heck",

"quote",
"syn 2.0.106",
"syn 2.0.109",
]

@@ -852,5 +859,5 @@

name = "quote"
version = "1.0.40"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [

@@ -908,5 +915,5 @@ "proc-macro2",

name = "redox_syscall"
version = "0.5.17"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [

@@ -918,5 +925,5 @@ "bitflags",

name = "regex"
version = "1.11.2"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [

@@ -931,5 +938,5 @@ "aho-corasick",

name = "regex-automata"
version = "0.4.10"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [

@@ -943,11 +950,11 @@ "aho-corasick",

name = "regex-syntax"
version = "0.8.6"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rustfft"
version = "6.4.0"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f140db74548f7c9d7cce60912c9ac414e74df5e718dc947d514b051b42f3f4"
checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89"
dependencies = [

@@ -964,5 +971,5 @@ "num-complex",

name = "rustix"
version = "1.0.8"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [

@@ -973,6 +980,12 @@ "bitflags",

"linux-raw-sys",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "same-file"

@@ -1003,6 +1016,7 @@ version = "1.0.6"

name = "serde"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",

@@ -1012,10 +1026,19 @@ ]

[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
"syn 2.0.109",
]

@@ -1041,2 +1064,8 @@

[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "smallvec"

@@ -1083,5 +1112,5 @@ version = "1.15.1"

name = "syn"
version = "2.0.106"
version = "2.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
dependencies = [

@@ -1106,33 +1135,14 @@ "proc-macro2",

name = "target-lexicon"
version = "0.13.2"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "time"
version = "0.3.43"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"num-conv",

@@ -1325,5 +1335,5 @@ "powerfmt",

name = "typenum"
version = "1.18.0"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"

@@ -1338,11 +1348,11 @@ [[package]]

name = "unicode-ident"
version = "1.0.18"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
dependencies = [

@@ -1388,7 +1398,7 @@ "tinyvec",

name = "winapi-util"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]

@@ -1398,5 +1408,5 @@

name = "windows-link"
version = "0.1.3"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"

@@ -1409,19 +1419,12 @@ [[package]]

dependencies = [
"windows-targets 0.53.3",
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
"windows-link",
]

@@ -1431,15 +1434,15 @@

name = "windows-targets"
version = "0.53.3"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]

@@ -1449,101 +1452,53 @@

name = "windows_aarch64_gnullvm"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "xattr"
version = "1.5.1"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [

@@ -1556,5 +1511,5 @@ "libc",

name = "zerocopy"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [

@@ -1566,9 +1521,9 @@ "zerocopy-derive",

name = "zerocopy-derive"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
"syn 2.0.109",
]

@@ -15,3 +15,3 @@ [package]

[dependencies]
pyo3 = { version ="0.25.0", features = ["extension-module", "abi3-py37"] }
pyo3 = { version ="0.27.1", features = ["extension-module", "abi3-py37"] }
ms_toollib_original = { path = "../base", features = ["py"], package="ms_toollib" }

@@ -25,8 +25,7 @@ itertools = { version ="0.6.0" }

# [profile.release]
# opt-level = 'z'
# lto = true
# codegen-units = 1
# panic = 'abort'
# 4018
[profile.release]
opt-level = "z" # 优化大小
lto = true # 链接时优化,跨crate优化代码
panic = "abort" # 禁用恐慌的展开信息
strip = true # 剥离调试符号
use crate::PyGameBoard;
use ms_toollib_original::videos::base_video::{KeyDynamicParams, NewBaseVideo2};
use ms_toollib_original::{videos::base_video::VideoActionStateRecorder, *};
use pyo3::prelude::*;
use ms_toollib_original::videos::base_video::NewBaseVideo2;
use ms_toollib_original::videos::types::KeyDynamicParams;
use ms_toollib_original::*;
use pyo3::{IntoPyObjectExt, prelude::*};

@@ -97,2 +98,224 @@ #[pyclass(name = "SafeBoardRow")]

#[pyclass(name = "Event", unsendable)]
#[derive(Clone)]
pub struct PyEvent {
pub core: Event,
}
#[pymethods]
impl PyEvent {
// ---------- 构造函数 ----------
#[staticmethod]
pub fn mouse(event: PyMouseEvent) -> Self {
Self { core: Event::Mouse(event.core) }
}
#[staticmethod]
pub fn game_state(event: PyGameStateEvent) -> Self {
Self { core: Event::GameState(event.core) }
}
#[staticmethod]
pub fn board(event: PyBoardEvent) -> Self {
Self { core: Event::Board(event.core) }
}
#[staticmethod]
pub fn index(event: PyIndexEvent) -> Self {
Self { core: Event::Index(event.core) }
}
// ---------- 类型判断 ----------
pub fn is_mouse(&self) -> bool {
matches!(self.core, Event::Mouse(_))
}
pub fn is_game_state(&self) -> bool {
matches!(self.core, Event::GameState(_))
}
pub fn is_board(&self) -> bool {
matches!(self.core, Event::Board(_))
}
pub fn is_index(&self) -> bool {
matches!(self.core, Event::Index(_))
}
// ---------- 解包 ----------
pub fn unwrap_mouse(&self) -> Option<PyMouseEvent> {
match &self.core {
Event::Mouse(e) => Some(PyMouseEvent { core: e.clone() }),
_ => None,
}
}
pub fn unwrap_board(&self) -> Option<PyBoardEvent> {
match &self.core {
Event::Board(e) => Some(PyBoardEvent { core: e.clone() }),
_ => None,
}
}
pub fn unwrap_index(&self) -> Option<PyIndexEvent> {
match &self.core {
Event::Index(e) => Some(PyIndexEvent { core: e.clone() }),
_ => None,
}
}
pub fn unwrap_game_state(&self) -> Option<PyGameStateEvent> {
match &self.core {
Event::GameState(e) => Some(PyGameStateEvent { core: e.clone() }),
_ => None,
}
}
pub fn __repr__(&self) -> String {
match &self.core {
Event::Mouse(_) => "Event.Mouse".to_string(),
Event::GameState(_) => "Event.GameState".to_string(),
Event::Board(_) => "Event.Board".to_string(),
Event::Index(_) => "Event.Index".to_string(),
}
}
}
#[pyclass(name = "MouseEvent", unsendable)]
#[derive(Clone)]
pub struct PyMouseEvent {
pub core: MouseEvent,
}
#[pymethods]
impl PyMouseEvent {
#[new]
pub fn new(mouse: String, x: u16, y: u16) -> Self {
Self { core: MouseEvent { mouse, x, y } }
}
#[getter]
pub fn mouse(&self) -> String {
self.core.mouse.clone()
}
#[getter]
pub fn x(&self) -> u16 {
self.core.x
}
#[getter]
pub fn y(&self) -> u16 {
self.core.y
}
pub fn __repr__(&self) -> String {
format!("MouseEvent(mouse='{}', x={}, y={})", self.core.mouse, self.core.x, self.core.y)
}
}
#[pyclass(name = "GameStateEvent", unsendable)]
#[derive(Clone)]
pub struct PyGameStateEvent {
pub core: GameStateEvent,
}
#[pymethods]
impl PyGameStateEvent {
#[new]
pub fn new(game_state: String) -> Self {
Self { core: GameStateEvent { game_state } }
}
#[getter]
pub fn game_state(&self) -> String {
self.core.game_state.clone()
}
pub fn __repr__(&self) -> String {
format!("GameStateEvent(game_state='{}')", self.core.game_state)
}
}
#[pyclass(name = "BoardEvent", unsendable)]
#[derive(Clone)]
pub struct PyBoardEvent {
pub core: BoardEvent,
}
#[pymethods]
impl PyBoardEvent {
#[new]
pub fn new(board: String, row_id: u8, column_id: u8) -> Self {
Self { core: BoardEvent { board, row_id, column_id } }
}
#[getter]
pub fn board(&self) -> String {
self.core.board.clone()
}
#[getter]
pub fn row_id(&self) -> u8 {
self.core.row_id
}
#[getter]
pub fn column_id(&self) -> u8 {
self.core.column_id
}
pub fn __repr__(&self) -> String {
format!(
"BoardEvent(board='{}', row_id={}, column_id={})",
self.core.board, self.core.row_id, self.core.column_id
)
}
}
#[pyclass(name = "IndexEvent", unsendable)]
#[derive(Clone)]
pub struct PyIndexEvent {
pub core: IndexEvent,
}
#[pymethods]
impl PyIndexEvent {
#[new]
pub fn new(key: String, value: Py<PyAny>, py: Python<'_>) -> PyResult<Self> {
// 允许 Python 传入 float 或 str
if let Ok(v) = value.extract::<f64>(py) {
Ok(Self { core: IndexEvent { key, value: IndexValue::Number(v) } })
} else if let Ok(v) = value.extract::<String>(py) {
Ok(Self { core: IndexEvent { key, value: IndexValue::String(v) } })
} else {
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"value must be float or str",
))
}
}
#[getter]
pub fn key(&self) -> String {
self.core.key.clone()
}
#[getter]
pub fn value(&self, py: Python<'_>) -> Py<PyAny> {
match &self.core.value {
IndexValue::Number(f) => f.into_py_any(py).unwrap(),
IndexValue::String(s) => s.clone().into_py_any(py).unwrap(),
}
}
pub fn __repr__(&self) -> String {
match &self.core.value {
IndexValue::Number(f) => format!("IndexEvent(key='{}', value={})", self.core.key, f),
IndexValue::String(s) => format!("IndexEvent(key='{}', value='{}')", self.core.key, s),
}
}
}
#[pyclass(name = "VideoActionStateRecorder", unsendable)]

@@ -110,14 +333,20 @@ pub struct PyVideoActionStateRecorder {

#[getter]
fn get_x(&self) -> PyResult<u16> {
Ok(self.core.x)
fn get_event(&self) -> PyResult<PyEvent> {
Ok(PyEvent {
core: self.core.event.clone().unwrap(),
})
}
// #[getter]
// fn get_x(&self) -> PyResult<u16> {
// Ok(self.core.x)
// }
// #[getter]
// fn get_y(&self) -> PyResult<u16> {
// Ok(self.core.y)
// }
// #[getter]
// fn get_mouse(&self) -> PyResult<String> {
// Ok(self.core.mouse.clone())
// }
#[getter]
fn get_y(&self) -> PyResult<u16> {
Ok(self.core.y)
}
#[getter]
fn get_mouse(&self) -> PyResult<String> {
Ok(self.core.mouse.clone())
}
#[getter]
fn get_useful_level(&self) -> PyResult<u8> {

@@ -220,2 +449,5 @@ Ok(self.core.useful_level)

}
pub fn step_game_state(&mut self, e: &str) {
self.core.step_game_state(e).unwrap();
}
pub fn reset(&mut self, row: usize, column: usize, pix_size: u8) {

@@ -463,3 +695,3 @@ self.core.reset(row, column, pix_size);

#[getter]
fn get_pluck(&self) -> PyResult<f64> {
fn get_pluck(&mut self) -> PyResult<f64> {
Ok(self.core.get_pluck().unwrap())

@@ -466,0 +698,0 @@ }

@@ -10,7 +10,7 @@ use ms_toollib_original::*;

// 似乎没用?
impl PyGameBoard {
pub fn set_core(&mut self, value: GameBoard) {
self.core = value;
}
}
// impl PyGameBoard {
// pub fn set_core(&mut self, value: GameBoard) {
// self.core = value;
// }
// }

@@ -17,0 +17,0 @@ #[pymethods]

@@ -39,2 +39,5 @@ use pyo3::exceptions::PyRuntimeError;

mod evfs;
pub use evfs::{PyEvfs, PyEvfsCell};
// pip install maturin

@@ -510,3 +513,5 @@ // maturin publish --manylinux 2014

m.add_class::<PyKeyDynamicParams>()?;
m.add_class::<PyEvfs>()?;
m.add_class::<PyEvfsCell>()?;
Ok(())
}

Sorry, the diff of this file is too big to display