From 77275a43b04a076d548b43d5e7b70e5c2fee82a9 Mon Sep 17 00:00:00 2001 From: Nervonment Date: Fri, 21 Jun 2024 17:43:27 +0800 Subject: [PATCH 1/2] hint of basic fish --- package.json | 2 +- src-tauri/Cargo.lock | 16 ++++- src-tauri/src/hint.rs | 57 ++++++++++++++++-- src-tauri/src/hint/fish.rs | 118 +++++++++++++++++++++++++++++++++++++ src-tauri/src/main.rs | 10 ++-- src-tauri/tauri.conf.json | 2 +- 6 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 src-tauri/src/hint/fish.rs diff --git a/package.json b/package.json index b85ce23..fb4c35a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sudoxide", - "version": "0.3.4", + "version": "0.3.5", "private": true, "scripts": { "dev": "next dev", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2d28adc..2a51788 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -508,7 +508,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -529,7 +529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -1690,6 +1690,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -3148,9 +3157,10 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "sudoku" version = "0.1.0" -source = "git+https://github.com/Nervonment/sudoku.git#38e099da81fdafb1d293be69bcf1d9ad01aaaa23" +source = "git+https://github.com/Nervonment/sudoku.git#ecc749191e1b8026582066cbad0d47b08545636e" dependencies = [ "criterion", + "itertools 0.13.0", "rand 0.8.5", ] diff --git a/src-tauri/src/hint.rs b/src-tauri/src/hint.rs index d68226b..4ba8095 100644 --- a/src-tauri/src/hint.rs +++ b/src-tauri/src/hint.rs @@ -2,6 +2,7 @@ use serde::Serialize; use sudoku::state::full_state::FullState; use sudoku::techniques::{DirectOption, House, ReducingCandidatesOption, Technique}; +pub mod fish; pub mod hidden_subsets; pub mod locked_candidates; pub mod naked_subsets; @@ -35,19 +36,19 @@ pub trait GetHint: Technique { fn get_hint(&self) -> Option; } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct Segment { pub text: String, pub color: Color, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct Element { pub kind: ElementType, pub color: Color, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub enum Color { TextDefault, House1, @@ -58,7 +59,7 @@ pub enum Color { CandidateToRemove, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub enum ElementType { #[serde(with = "HouseDef")] House(House), @@ -92,6 +93,51 @@ pub trait To { fn candidate_to_reserve(&self) -> T; } +impl To for &str { + fn text_default(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::TextDefault, + } + } + fn house1(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::House1, + } + } + fn house2(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::House2, + } + } + fn cell1(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::Cell1, + } + } + fn num_to_fill(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::NumToFill, + } + } + fn candidate_to_remove(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::CandidateToRemove, + } + } + fn candidate_to_reserve(&self) -> Segment { + Segment { + text: (*self).to_owned(), + color: Color::CandidateToReserve, + } + } +} + impl To for String { fn text_default(&self) -> Segment { Segment { @@ -227,7 +273,6 @@ impl To for (usize, usize) { } } - impl To for (usize, usize, i8) { fn text_default(&self) -> Element { Element { @@ -271,4 +316,4 @@ impl To for (usize, usize, i8) { color: Color::CandidateToReserve, } } -} \ No newline at end of file +} diff --git a/src-tauri/src/hint/fish.rs b/src-tauri/src/hint/fish.rs new file mode 100644 index 0000000..7bf0b97 --- /dev/null +++ b/src-tauri/src/hint/fish.rs @@ -0,0 +1,118 @@ +use sudoku::{ + state::full_state::FullState, + techniques::{ + fish::{FishInfo, Jellyfish, Swordfish, XWing}, + ReducingCandidates, ReducingCandidatesOption, Technique, + }, +}; + +use super::{house_to_string, GetHint, Hint, HintOption, To}; + +fn fish_info_to_hint(info: FishInfo, option: ReducingCandidatesOption) -> Hint { + Hint { + name: match info.size { + 2 => "X-Wing", + 3 => "Swordfish", + 4 => "Jellyfish", + _ => "Fish", + } + .into(), + description: vec![ + "在".text_default(), + info.base_set + .iter() + .map(|house| house_to_string(*house)) + .collect::>() + .join(" ") + .house1(), + "中,所有可以填".text_default(), + info.candidate.to_string().candidate_to_reserve(), + "的格子都在".text_default(), + "它们".house1(), + "与".text_default(), + info.cover_set + .iter() + .map(|house| house_to_string(*house)) + .collect::>() + .join(" ") + .house2(), + "的".text_default(), + "交叉区域".cell1(), + "中。无论".text_default(), + info.candidate.to_string().candidate_to_reserve(), + "在".text_default(), + "交叉区域".cell1(), + "中如何排列,".text_default(), + info.cover_set + .iter() + .map(|house| house_to_string(*house)) + .collect::>() + .join(" ") + .house2(), + "中的".text_default(), + info.candidate.to_string().candidate_to_reserve(), + "都只能填在".text_default(), + "交叉区域".cell1(), + "中,进而可以从其他格子的候选数中移除".text_default(), + info.candidate.to_string().candidate_to_remove(), + "。".text_default(), + ], + visual_elements: vec![ + info.base_set + .iter() + .map(|house| house.house1()) + .collect::>(), + info.cover_set + .iter() + .map(|house| house.house2()) + .collect::>(), + info.overlap + .iter() + .map(|cell| cell.cell1()) + .collect::>(), + info.overlap + .iter() + .map(|(r, c)| (*r, *c, info.candidate).candidate_to_reserve()) + .collect::>(), + info.rem_cells + .iter() + .map(|(r, c)| (*r, *c, info.candidate).candidate_to_remove()) + .collect::>(), + ] + .concat(), + option: HintOption::ReducingCandidates(option), + } +} + +impl GetHint for XWing { + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + return Some(fish_info_to_hint(info, option)); + } + None + } +} + +impl GetHint for Swordfish { + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + return Some(fish_info_to_hint(info, option)); + } + None + } +} + +impl GetHint for Jellyfish { + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + return Some(fish_info_to_hint(info, option)); + } + None + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 11af61a..333d5b7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -13,10 +13,7 @@ use sudoku::{ solver::{advanced::AdvancedSolver, Solver}, state::full_state::FullState, techniques::{ - hidden_subsets::HiddenPair, - locked_candidates::{Claiming, Pointing}, - naked_subsets::{NakedPair, NakedSubset}, - singles::{HiddenSingle, NakedSingle}, + fish::{Jellyfish, Swordfish, XWing}, hidden_subsets::HiddenPair, locked_candidates::{Claiming, Pointing}, naked_subsets::{NakedPair, NakedSubset}, singles::{HiddenSingle, NakedSingle} }, Grid, }; @@ -98,14 +95,17 @@ fn get_hint(grid: [[i8; 9]; 9], candidates: [[[bool; 10]; 9]; 9]) -> (Option Date: Fri, 21 Jun 2024 17:52:11 +0800 Subject: [PATCH 2/2] fix bug: refreshing --- app/start/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/start/page.js b/app/start/page.js index a1d706d..1dc820a 100644 --- a/app/start/page.js +++ b/app/start/page.js @@ -393,7 +393,7 @@ export default function Start() { /> } desc={"撤销(Z)"} onClick={handleUndo} /> } desc={"重做(X)"} onClick={handleRedo} /> - } desc={"换一道题"} onClick={newPuzzle} /> + } desc={"换一道题"} onClick={() => { if (grid) newPuzzle(); }} /> } desc={"清空"} onClick={clear} /> } desc={"返回首页"} onClick={() => router.replace("/")} />