ms-toollib
Advanced tools
+307
| 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); | ||
| } | ||
| } |
+6
-1
@@ -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 @@ |
| #[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 @@ }); |
+113
-55
@@ -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 |
+21
-15
@@ -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 @@ }); |
+562
-140
@@ -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(); | ||
| } | ||
+1
-1
| 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 |
+1
-1
| [project] | ||
| name = "ms_toollib" | ||
| version = "1.4.19" | ||
| version = "1.5.0" | ||
| description = "Algorithms for minesweeper." | ||
@@ -5,0 +5,0 @@ readme = "readme.md" |
+177
-222
@@ -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
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
672331
7.46%49
16.67%