diff --git a/app/page.js b/app/page.js index 437f375..be8ed40 100644 --- a/app/page.js +++ b/app/page.js @@ -25,6 +25,7 @@ import { useEffect, useState } from "react"; export default function Home() { const [difficulty, setDifficulty] = useState(2); const [markingAssist, setMarkingAssist] = useState(false); + const [beginWithMarks, setBeginWithMarks] = useState(false); const [appVersion, setAppversion] = useState(""); const [updateDialogOpen, setUpdateDialogOpen] = useState(false); const [updateManifest, setUpdateManifest] = useState({}); @@ -34,6 +35,7 @@ export default function Home() { useEffect(() => { invoke('get_difficulty').then((difficulty) => setDifficulty(difficulty)); invoke('get_marking_assist').then((markingAssist) => setMarkingAssist(markingAssist)); + invoke('get_begin_with_marks').then((beginWithMarks) => setBeginWithMarks(beginWithMarks)); }, []); useEffect(() => { @@ -105,7 +107,7 @@ export default function Home() { -

开启标记辅助后,开局时会自动标记上所有候选数字

+

开启标记辅助后,每个格子中的候选数会自动更新

+
+ + +
+ +
+
+ +

开启后,开局时会自动标记上所有候选数字

+
+
+ { + setBeginWithMarks(checked); + invoke('set_begin_with_marks', { beginWithMarks: checked }); + }} + /> +
diff --git a/app/start/page.js b/app/start/page.js index 76b400b..89abc5d 100644 --- a/app/start/page.js +++ b/app/start/page.js @@ -31,6 +31,7 @@ export default function Start() { const [usedAssist, setUsedAssist] = useState(false); const markingAssistRef = useRef(false); // 是否开启标记辅助 + const beginWithMarksRef = useRef(false); // 是否开启开局标记 const historyRef = useRef([]); const futureRef = useRef([]); @@ -57,13 +58,15 @@ export default function Start() { useEffect(() => { invoke('get_difficulty').then((difficulty) => setDifficulty(difficulty)); invoke('get_marking_assist').then((markingAssist) => { markingAssistRef.current = markingAssist; setUsedAssist(markingAssist); }); + invoke('get_begin_with_marks').then((beginWithMarks) => { beginWithMarksRef.current = beginWithMarks; }); }, []); const init = useCallback((grid) => { setGrid(grid); setMaxCandidates(getMaxCandidates(grid)); - if (markingAssistRef.current) { - setMarkedCandidates(Array.from({ length: 9 }, (v) => Array.from({ length: 9 }, (v) => Array.from({ length: 10 }, (v) => true)))); + if (beginWithMarksRef.current) { + setMarkedCandidates(getMaxCandidates(grid)); + // setMarkedCandidates(Array.from({ length: 9 }, (v) => Array.from({ length: 9 }, (v) => Array.from({ length: 10 }, (v) => true)))); } else { setMarkedCandidates(Array.from({ length: 9 }, (v) => Array.from({ length: 9 }, (v) => Array.from({ length: 10 }, (v) => false)))); } @@ -226,8 +229,9 @@ export default function Start() { } // 标记候选数 else { - // 只能标记 maxCandidates[r][c] 中的数字 - if (grid[r][c].value == 0 && (maxCandidates[r][c][num] || event.key == ' ')) { + // 没有开启标记辅助时,可以标记任意数字 + // 开启时,只能标记 maxCandidates[r][c] 中的数字 + if (grid[r][c].value == 0 && (!markingAssistRef.current || maxCandidates[r][c][num] || event.key == ' ')) { if (event.key == ' ') { // 按空格键 if (!markedCandidates[r][c].every((is, num) => num == 0 || !is)) { pushHistory(grid, markedCandidates); @@ -271,7 +275,7 @@ export default function Start() { if (!finished && grid) invoke('get_hint', { grid: grid.map((row) => row.map((cell) => cell.value)), - candidates: maskedCandidates + candidates: markingAssistRef.current ? maskedCandidates : markedCandidates }).then((hint) => { setUsedHint(true); setHint(hint[0]); @@ -309,7 +313,7 @@ export default function Start() { <> for DirectOption { - fn from(value: sudoku::techniques::DirectOption) -> Self { - Self(value.0, value.1, value.2) - } -} +#[serde(remote = "DirectOption")] +pub struct DirectOptionDef(pub usize, pub usize, pub i8); #[derive(Serialize)] -pub struct ReducingCandidatesOption(pub Vec<(Vec<(usize, usize)>, Vec)>); -impl From for ReducingCandidatesOption { - fn from(value: sudoku::techniques::ReducingCandidatesOption) -> Self { - Self(value.0) - } -} +#[serde(remote = "ReducingCandidatesOption")] +pub struct ReducingCandidatesOptionDef(pub Vec<(Vec<(usize, usize)>, Vec)>); #[derive(Serialize)] pub enum HintOption { + #[serde(with = "DirectOptionDef")] Direct(DirectOption), + #[serde(with = "ReducingCandidatesOptionDef")] ReducingCandidates(ReducingCandidatesOption), } @@ -37,8 +31,8 @@ pub struct Hint { pub option: HintOption, } -pub trait GetHint { - fn get_hint(state: &FullState) -> Option; +pub trait GetHint: Technique { + fn get_hint(&self) -> Option; } #[derive(Serialize)] @@ -66,30 +60,24 @@ pub enum Color { #[derive(Serialize)] pub enum ElementType { + #[serde(with = "HouseDef")] House(House), Cell(usize, usize), Candidate(usize, usize, i8), } #[derive(Serialize)] -pub enum House { +#[serde(remote = "House")] +pub enum HouseDef { Row(usize), Column(usize), Block(usize), } -fn house_to_string(house: sudoku::techniques::House) -> String { +fn house_to_string(house: House) -> String { match house { sudoku::techniques::House::Row(idx) => format!("第{}行", idx + 1), sudoku::techniques::House::Column(idx) => format!("第{}列", idx + 1), sudoku::techniques::House::Block(idx) => format!("第{}宫", idx + 1), } } - -fn house_to_house(house: sudoku::techniques::House) -> House { - match house { - sudoku::techniques::House::Row(idx) => House::Row(idx), - sudoku::techniques::House::Column(idx) => House::Column(idx), - sudoku::techniques::House::Block(idx) => House::Block(idx), - } -} diff --git a/src-tauri/src/hint/hidden_subsets.rs b/src-tauri/src/hint/hidden_subsets.rs index 82ffcaf..8096a3d 100644 --- a/src-tauri/src/hint/hidden_subsets.rs +++ b/src-tauri/src/hint/hidden_subsets.rs @@ -1,172 +1,146 @@ use sudoku::{ state::full_state::FullState, - techniques::{ - hidden_subsets::{HiddenPairBlock, HiddenPairColumn, HiddenPairInfo, HiddenPairRow}, - Technique, - }, + techniques::{hidden_subsets::HiddenPair, ReducingCandidates, Technique}, }; -use super::{ - house_to_house, house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, - ReducingCandidatesOption, Segment, -}; - -fn hidden_pair_to_hint( - info: HiddenPairInfo, - option: sudoku::techniques::ReducingCandidatesOption, -) -> Hint { - let mut visual_elements = vec![ - Element { - kind: ElementType::House(house_to_house(info.house)), - color: Color::House1, - }, - Element { - kind: ElementType::Candidate(info.rem_cell_1.0, info.rem_cell_1.1, info.nums[0]), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Candidate(info.rem_cell_1.0, info.rem_cell_1.1, info.nums[1]), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Candidate(info.rem_cell_2.0, info.rem_cell_2.1, info.nums[0]), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Candidate(info.rem_cell_2.0, info.rem_cell_2.1, info.nums[1]), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Cell(info.rem_cell_1.0, info.rem_cell_1.1), - color: Color::Cell1, - }, - Element { - kind: ElementType::Cell(info.rem_cell_2.0, info.rem_cell_2.1), - color: Color::Cell1, - }, - ]; - for num in info.rem_nums_1 { - visual_elements.push(Element { - kind: ElementType::Candidate(info.rem_cell_1.0, info.rem_cell_1.1, num), - color: Color::CandidateToRemove, - }); - } - for num in info.rem_nums_2 { - visual_elements.push(Element { - kind: ElementType::Candidate(info.rem_cell_2.0, info.rem_cell_2.1, num), - color: Color::CandidateToRemove, - }); - } +use super::{house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, Segment}; - Hint { - name: "Hidden Pair".into(), - description: vec![ - Segment { - text: house_to_string(info.house), - color: Color::House1, - }, - Segment { - text: "中,".into(), - color: Color::TextDefault, - }, - Segment { - text: info.nums[0].to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: "和".into(), - color: Color::TextDefault, - }, - Segment { - text: info.nums[1].to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: "只能填在".into(), - color: Color::TextDefault, - }, - Segment { - text: "这两格".into(), - color: Color::Cell1, - }, - Segment { - text: ",所以".into(), - color: Color::TextDefault, - }, - Segment { - text: "这两格".into(), - color: Color::Cell1, - }, - Segment { - text: "也只可能填".into(), - color: Color::TextDefault, - }, - Segment { - text: info.nums[0].to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: "和".into(), - color: Color::TextDefault, - }, - Segment { - text: info.nums[1].to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: ",".into(), - color: Color::TextDefault, - }, - Segment { - text: "其他的候选数字".into(), - color: Color::CandidateToRemove, - }, - Segment { - text: "可以删去。".into(), - color: Color::TextDefault, - }, - ], - visual_elements, - option: HintOption::ReducingCandidates(ReducingCandidatesOption::from(option)), - } -} - -pub struct HiddenPair; impl GetHint for HiddenPair { - fn get_hint(state: &FullState) -> Option { - let res = HiddenPairBlock::check(state); - if res.0.is_some() { - return Some( - hidden_pair_to_hint( - res.0.clone().unwrap(), - , - >>::into(res) - .unwrap() - .into(), - ), - ); - } - let res = HiddenPairRow::check(state); - if res.0.is_some() { - return Some(hidden_pair_to_hint(res.0.clone().unwrap(), - >>::into(res).unwrap().into() - )); - } - let res = HiddenPairColumn::check(state); - if res.0.is_some() { - return Some( - hidden_pair_to_hint( - res.0.clone().unwrap(), - , - >>::into(res) - .unwrap() - .into(), - ), - ); - } + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + let mut visual_elements = vec![ + Element { + kind: ElementType::House(info.house), + color: Color::House1, + }, + Element { + kind: ElementType::Candidate( + info.rem_cell_1.0, + info.rem_cell_1.1, + info.nums[0], + ), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Candidate( + info.rem_cell_1.0, + info.rem_cell_1.1, + info.nums[1], + ), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Candidate( + info.rem_cell_2.0, + info.rem_cell_2.1, + info.nums[0], + ), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Candidate( + info.rem_cell_2.0, + info.rem_cell_2.1, + info.nums[1], + ), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Cell(info.rem_cell_1.0, info.rem_cell_1.1), + color: Color::Cell1, + }, + Element { + kind: ElementType::Cell(info.rem_cell_2.0, info.rem_cell_2.1), + color: Color::Cell1, + }, + ]; + for num in info.rem_nums_1 { + visual_elements.push(Element { + kind: ElementType::Candidate(info.rem_cell_1.0, info.rem_cell_1.1, num), + color: Color::CandidateToRemove, + }); + } + for num in info.rem_nums_2 { + visual_elements.push(Element { + kind: ElementType::Candidate(info.rem_cell_2.0, info.rem_cell_2.1, num), + color: Color::CandidateToRemove, + }); + } + return Some(Hint { + name: "Hidden Pair".into(), + description: vec![ + Segment { + text: house_to_string(info.house), + color: Color::House1, + }, + Segment { + text: "中,".into(), + color: Color::TextDefault, + }, + Segment { + text: info.nums[0].to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: "和".into(), + color: Color::TextDefault, + }, + Segment { + text: info.nums[1].to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: "只能填在".into(), + color: Color::TextDefault, + }, + Segment { + text: "这两格".into(), + color: Color::Cell1, + }, + Segment { + text: ",所以".into(), + color: Color::TextDefault, + }, + Segment { + text: "这两格".into(), + color: Color::Cell1, + }, + Segment { + text: "也只可能填".into(), + color: Color::TextDefault, + }, + Segment { + text: info.nums[0].to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: "和".into(), + color: Color::TextDefault, + }, + Segment { + text: info.nums[1].to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: ",".into(), + color: Color::TextDefault, + }, + Segment { + text: "其他的候选数字".into(), + color: Color::CandidateToRemove, + }, + Segment { + text: "可以删去。".into(), + color: Color::TextDefault, + }, + ], + visual_elements, + option: HintOption::ReducingCandidates(option), + }); + } None } } diff --git a/src-tauri/src/hint/locked_candidates.rs b/src-tauri/src/hint/locked_candidates.rs index d431fa2..b28671f 100644 --- a/src-tauri/src/hint/locked_candidates.rs +++ b/src-tauri/src/hint/locked_candidates.rs @@ -1,23 +1,27 @@ -use sudoku::{state::full_state::FullState, techniques::Technique, utils::coord_2_block}; - -use super::{ - house_to_house, house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, - ReducingCandidatesOption, Segment, +use sudoku::{ + state::full_state::FullState, + techniques::{ + locked_candidates::{Claiming, Pointing}, + ReducingCandidates, Technique, + }, + utils::coord_2_block, }; -pub struct Pointing; +use super::{house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, Segment}; + impl GetHint for Pointing { - fn get_hint(state: &FullState) -> Option { - let res = sudoku::techniques::locked_candidates::Pointing::check(state); - if res.0.is_some() { - let info = res.0.clone().unwrap(); + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + let mut visual_elements = vec![ Element { kind: ElementType::House(super::House::Block(info.block)), color: Color::House1, }, Element { - kind: ElementType::House(house_to_house(info.rem_house)), + kind: ElementType::House(info.rem_house), color: Color::House2, }, ]; @@ -90,32 +94,27 @@ impl GetHint for Pointing { color: Color::TextDefault, }, ], - visual_elements: visual_elements, - option: HintOption::ReducingCandidates(ReducingCandidatesOption::from( - , - >>::into(res) - .unwrap(), - )), + visual_elements, + option: HintOption::ReducingCandidates(option), }); } None } } -pub struct Claiming; impl GetHint for Claiming { - fn get_hint(state: &FullState) -> Option { - let res = sudoku::techniques::locked_candidates::Claiming::check(state); - if res.0.is_some() { - let info = res.0.clone().unwrap(); + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + let mut visual_elements = vec![ Element { kind: ElementType::House(super::House::Block(info.rem_block)), color: Color::House2, }, Element { - kind: ElementType::House(house_to_house(info.house)), + kind: ElementType::House(info.house), color: Color::House1, }, ]; @@ -195,13 +194,8 @@ impl GetHint for Claiming { color: Color::TextDefault, }, ], - visual_elements: visual_elements, - option: HintOption::ReducingCandidates(ReducingCandidatesOption::from( - , - >>::into(res) - .unwrap(), - )), + visual_elements, + option: HintOption::ReducingCandidates(option), }); } None diff --git a/src-tauri/src/hint/naked_subsets.rs b/src-tauri/src/hint/naked_subsets.rs index 01a3571..427c482 100644 --- a/src-tauri/src/hint/naked_subsets.rs +++ b/src-tauri/src/hint/naked_subsets.rs @@ -1,185 +1,239 @@ use sudoku::{ state::full_state::FullState, techniques::{ - naked_subsets::{NakedPairBlock, NakedPairColumn, NakedPairInfo, NakedPairRow}, - Technique, + naked_subsets::{NakedPair, NakedSubset}, + ReducingCandidates, Technique, }, }; -use super::{ - house_to_house, house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, - ReducingCandidatesOption, Segment, -}; +use super::{house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, Segment}; -fn naked_pair_to_hint( - info: NakedPairInfo, - option: sudoku::techniques::ReducingCandidatesOption, -) -> Hint { - let mut visual_elements = vec![ - Element { - kind: ElementType::House(house_to_house(info.house)), - color: Color::House1, - }, - Element { - kind: ElementType::Cell(info.cells[0].0, info.cells[0].1), - color: Color::Cell1, - }, - Element { - kind: ElementType::Cell(info.cells[1].0, info.cells[1].1), - color: Color::Cell1, - }, - Element { - kind: ElementType::Candidate(info.cells[0].0, info.cells[0].1, info.rem_num_1), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Candidate(info.cells[0].0, info.cells[0].1, info.rem_num_2), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Candidate(info.cells[1].0, info.cells[1].1, info.rem_num_1), - color: Color::CandidateToReserve, - }, - Element { - kind: ElementType::Candidate(info.cells[1].0, info.cells[1].1, info.rem_num_2), - color: Color::CandidateToReserve, - }, - ]; - for cell in info.rem_cells_1 { - visual_elements.push(Element { - kind: ElementType::Candidate(cell.0, cell.1, info.rem_num_1), - color: Color::CandidateToRemove, - }); - } - for cell in info.rem_cells_2 { - visual_elements.push(Element { - kind: ElementType::Candidate(cell.0, cell.1, info.rem_num_2), - color: Color::CandidateToRemove, - }); +impl GetHint for NakedPair { + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + + let mut visual_elements = vec![ + Element { + kind: ElementType::House(info.house), + color: Color::House1, + }, + Element { + kind: ElementType::Cell(info.cells[0].0, info.cells[0].1), + color: Color::Cell1, + }, + Element { + kind: ElementType::Cell(info.cells[1].0, info.cells[1].1), + color: Color::Cell1, + }, + Element { + kind: ElementType::Candidate(info.cells[0].0, info.cells[0].1, info.rem_num_1), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Candidate(info.cells[0].0, info.cells[0].1, info.rem_num_2), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Candidate(info.cells[1].0, info.cells[1].1, info.rem_num_1), + color: Color::CandidateToReserve, + }, + Element { + kind: ElementType::Candidate(info.cells[1].0, info.cells[1].1, info.rem_num_2), + color: Color::CandidateToReserve, + }, + ]; + for cell in info.rem_cells_1 { + visual_elements.push(Element { + kind: ElementType::Candidate(cell.0, cell.1, info.rem_num_1), + color: Color::CandidateToRemove, + }); + } + for cell in info.rem_cells_2 { + visual_elements.push(Element { + kind: ElementType::Candidate(cell.0, cell.1, info.rem_num_2), + color: Color::CandidateToRemove, + }); + } + + return Some(Hint { + name: "Naked Pair".into(), + description: vec![ + Segment { + text: house_to_string(info.house), + color: Color::House1, + }, + Segment { + text: "中,".into(), + color: Color::TextDefault, + }, + Segment { + text: "这两格".into(), + color: Color::Cell1, + }, + Segment { + text: "只能填".into(), + color: Color::TextDefault, + }, + Segment { + text: info.rem_num_1.to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: "和".into(), + color: Color::TextDefault, + }, + Segment { + text: info.rem_num_2.to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: ",所以".into(), + color: Color::TextDefault, + }, + Segment { + text: info.rem_num_1.to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: "和".into(), + color: Color::TextDefault, + }, + Segment { + text: info.rem_num_2.to_string(), + color: Color::CandidateToReserve, + }, + Segment { + text: "也只可能填在".into(), + color: Color::TextDefault, + }, + Segment { + text: "这两格".into(), + color: Color::Cell1, + }, + Segment { + text: ",进而可以将".into(), + color: Color::TextDefault, + }, + Segment { + text: info.rem_num_1.to_string(), + color: Color::CandidateToRemove, + }, + Segment { + text: "和".into(), + color: Color::TextDefault, + }, + Segment { + text: info.rem_num_2.to_string(), + color: Color::CandidateToRemove, + }, + Segment { + text: "从其他的格子的候选数字中删去。".into(), + color: Color::TextDefault, + }, + ], + visual_elements, + option: HintOption::ReducingCandidates(option), + }); + } + None } +} + +impl GetHint for NakedSubset { + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + + let nums_str: Vec = info.nums.iter().map(|num| num.to_string()).collect(); + let nums_str = nums_str.join(" "); - Hint { - name: "Naked Pair".into(), - description: vec![ - Segment { - text: house_to_string(info.house), + let mut visual_elements = vec![Element { + kind: ElementType::House(info.house), color: Color::House1, - }, - Segment { - text: "中,".into(), - color: Color::TextDefault, - }, - Segment { - text: "这两格".into(), - color: Color::Cell1, - }, - Segment { - text: "只能填".into(), - color: Color::TextDefault, - }, - Segment { - text: info.rem_num_1.to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: "和".into(), - color: Color::TextDefault, - }, - Segment { - text: info.rem_num_2.to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: ",所以".into(), - color: Color::TextDefault, - }, - Segment { - text: info.rem_num_1.to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: "和".into(), - color: Color::TextDefault, - }, - Segment { - text: info.rem_num_2.to_string(), - color: Color::CandidateToReserve, - }, - Segment { - text: "也只可能填在".into(), - color: Color::TextDefault, - }, - Segment { - text: "这两格".into(), + }]; + + visual_elements.extend(info.cells.iter().map(|(r, c)| Element { + kind: ElementType::Cell(*r, *c), color: Color::Cell1, - }, - Segment { - text: ",进而可以将".into(), - color: Color::TextDefault, - }, - Segment { - text: info.rem_num_1.to_string(), - color: Color::CandidateToRemove, - }, - Segment { - text: "和".into(), - color: Color::TextDefault, - }, - Segment { - text: info.rem_num_2.to_string(), - color: Color::CandidateToRemove, - }, - Segment { - text: "从其他的格子的候选数字中删去。".into(), - color: Color::TextDefault, - }, - ], - visual_elements, - option: HintOption::ReducingCandidates(ReducingCandidatesOption::from(option)), - } -} + })); -pub struct NakedPair; -impl GetHint for NakedPair { - fn get_hint(state: &FullState) -> Option { - let res = NakedPairBlock::check(state); - if res.0.is_some() { - return Some( - naked_pair_to_hint( - res.0.clone().unwrap(), - , - >>::into(res) - .unwrap() - .into(), - ), - ); - } - let res = NakedPairRow::check(state); - if res.0.is_some() { - return Some(naked_pair_to_hint( - res.0.clone().unwrap(), - >>::into( - res, - ) - .unwrap() - .into(), - )); - } - let res = NakedPairColumn::check(state); - if res.0.is_some() { - return Some( - naked_pair_to_hint( - res.0.clone().unwrap(), - , - >>::into(res) - .unwrap() - .into(), - ), - ); - } + visual_elements.extend(info.nums.iter().flat_map(|num| { + info.cells.iter().map(|(r, c)| Element { + kind: ElementType::Candidate(*r, *c, *num), + color: Color::CandidateToReserve, + }) + })); + visual_elements.extend(info.removes.iter().flat_map(|(cells, num)| { + cells.iter().map(|(r, c)| Element { + kind: ElementType::Candidate(*r, *c, *num), + color: Color::CandidateToRemove, + }) + })); + + return Some(Hint { + name: match info.k { + 3 => "Naked Triplet".into(), + 4 => "Naked Quadruplet".into(), + _ => format!("Naked {}-Subset", info.k), + }, + description: vec![ + Segment { + text: house_to_string(info.house), + color: Color::House1, + }, + Segment { + text: "中,".into(), + color: Color::TextDefault, + }, + Segment { + text: format!("这{}格", info.k), + color: Color::Cell1, + }, + Segment { + text: "只能填".into(), + color: Color::TextDefault, + }, + Segment { + text: nums_str.clone(), + color: Color::CandidateToReserve, + }, + Segment { + text: ",所以".into(), + color: Color::TextDefault, + }, + Segment { + text: nums_str.clone(), + color: Color::CandidateToReserve, + }, + Segment { + text: "也只可能填在".into(), + color: Color::TextDefault, + }, + Segment { + text: format!("这{}格", info.k), + color: Color::Cell1, + }, + Segment { + text: ",进而可以将".into(), + color: Color::TextDefault, + }, + Segment { + text: nums_str, + color: Color::CandidateToRemove, + }, + Segment { + text: "从其他的格子的候选数字中删去。".into(), + color: Color::TextDefault, + }, + ], + visual_elements, + option: HintOption::ReducingCandidates(option), + }); + } None } } diff --git a/src-tauri/src/hint/singles.rs b/src-tauri/src/hint/singles.rs index f57813a..3e98e28 100644 --- a/src-tauri/src/hint/singles.rs +++ b/src-tauri/src/hint/singles.rs @@ -1,159 +1,107 @@ use sudoku::{ state::full_state::FullState, techniques::{ - singles::{ - HiddenSingleBlock, HiddenSingleColumn, HiddenSingleInfo, HiddenSingleRow, - NakedSingleInfo, - }, - Technique, + singles::{HiddenSingle, NakedSingle}, + Direct, Technique, }, }; -use super::{ - house_to_house, house_to_string, Color, DirectOption, Element, ElementType, GetHint, Hint, - HintOption, Segment, -}; - -fn hidden_single_to_hint(info: HiddenSingleInfo, option: sudoku::techniques::DirectOption) -> Hint { - Hint { - name: "Hidden Single".into(), - description: vec![ - Segment { - text: house_to_string(info.house), - color: Color::House1, - }, - Segment { - text: "中只有".into(), - color: Color::TextDefault, - }, - Segment { - text: "此格".into(), - color: Color::Cell1, - }, - Segment { - text: "可以填".into(), - color: Color::TextDefault, - }, - Segment { - text: format!("{}", info.fillable.2), - color: Color::NumToFill, - }, - Segment { - text: "。".into(), - color: Color::TextDefault, - }, - ], - visual_elements: vec![ - Element { - kind: ElementType::House(house_to_house(info.house)), - color: Color::House1, - }, - Element { - kind: ElementType::Cell(info.fillable.0, info.fillable.1), - color: Color::Cell1, - }, - Element { - kind: ElementType::Candidate(info.fillable.0, info.fillable.1, info.fillable.2), - color: Color::NumToFill, - }, - ], - option: HintOption::Direct(DirectOption::from(option)), - } -} +use super::{house_to_string, Color, Element, ElementType, GetHint, Hint, HintOption, Segment}; -pub struct HiddenSingle; impl GetHint for HiddenSingle { - fn get_hint(state: &FullState) -> Option { - let res = HiddenSingleBlock::check(state); - if res.0.is_some() { - let info = res.0; - return info.map(|info| { - hidden_single_to_hint( - info, - >>::into( - res, - ) - .unwrap(), - ) - }); - } - let res = HiddenSingleRow::check(state); - if res.0.is_some() { - let info = res.0; - return info.map(|info| { - hidden_single_to_hint( - info, - >>::into(res) - .unwrap(), - ) - }); - } - let res = HiddenSingleColumn::check(state); - if res.0.is_some() { - let info = res.0; - return info.map(|info| { - hidden_single_to_hint( - info, - >>::into( - res, - ) - .unwrap(), - ) + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + return Some(Hint { + name: "Hidden Single".into(), + description: vec![ + Segment { + text: house_to_string(info.house), + color: Color::House1, + }, + Segment { + text: "中只有".into(), + color: Color::TextDefault, + }, + Segment { + text: "此格".into(), + color: Color::Cell1, + }, + Segment { + text: "可以填".into(), + color: Color::TextDefault, + }, + Segment { + text: format!("{}", info.fillable.2), + color: Color::NumToFill, + }, + Segment { + text: "。".into(), + color: Color::TextDefault, + }, + ], + visual_elements: vec![ + Element { + kind: ElementType::House(info.house), + color: Color::House1, + }, + Element { + kind: ElementType::Cell(info.fillable.0, info.fillable.1), + color: Color::Cell1, + }, + Element { + kind: ElementType::Candidate( + info.fillable.0, + info.fillable.1, + info.fillable.2, + ), + color: Color::NumToFill, + }, + ], + option: HintOption::Direct(option), }); } None } } -fn naked_single_to_hint(info: NakedSingleInfo, option: sudoku::techniques::DirectOption) -> Hint { - Hint { - name: "Naked Single".into(), - description: vec![ - Segment { - text: "此格".into(), - color: Color::Cell1, - }, - Segment { - text: "只能填".into(), - color: Color::TextDefault, - }, - Segment { - text: format!("{}", info.0 .2), - color: Color::NumToFill, - }, - Segment { - text: "。".into(), - color: Color::TextDefault, - }, - ], - visual_elements: vec![ - Element { - kind: ElementType::Cell(info.0 .0, info.0 .1), - color: Color::Cell1, - }, - Element { - kind: ElementType::Candidate(info.0 .0, info.0 .1, info.0 .2), - color: Color::NumToFill, - }, - ], - option: HintOption::Direct(DirectOption::from(option)), - } -} - -pub struct NakedSingle; impl GetHint for NakedSingle { - fn get_hint(state: &FullState) -> Option { - let res = sudoku::techniques::singles::NakedSingle::check(state); - if res.0.is_some() { - let info = res.0; - return info.map(|info| { - naked_single_to_hint( - info, - , - >>::into(res) - .unwrap(), - ) + fn get_hint(&self) -> Option { + if >::appliable(&self) { + let info = self.0.clone().unwrap(); + let option = >::option(&self).unwrap(); + return Some(Hint { + name: "Naked Single".into(), + description: vec![ + Segment { + text: "此格".into(), + color: Color::Cell1, + }, + Segment { + text: "只能填".into(), + color: Color::TextDefault, + }, + Segment { + text: format!("{}", info.0 .2), + color: Color::NumToFill, + }, + Segment { + text: "。".into(), + color: Color::TextDefault, + }, + ], + visual_elements: vec![ + Element { + kind: ElementType::Cell(info.0 .0, info.0 .1), + color: Color::Cell1, + }, + Element { + kind: ElementType::Candidate(info.0 .0, info.0 .1, info.0 .2), + color: Color::NumToFill, + }, + ], + option: HintOption::Direct(option), }); } None diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8b60a78..904bfad 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,13 +1,7 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use app::hint::{ - hidden_subsets::HiddenPair, - locked_candidates::{Claiming, Pointing}, - naked_subsets::NakedPair, - singles::{HiddenSingle, NakedSingle}, - GetHint, Hint, -}; +use app::hint::{GetHint, Hint}; use serde::Serialize; use std::sync::{Arc, Mutex}; use sudoku::{ @@ -18,6 +12,12 @@ use sudoku::{ judge::judge_sudoku as judge, solver::{advanced::AdvancedSolver, Solver}, state::full_state::FullState, + techniques::{ + hidden_subsets::HiddenPair, + locked_candidates::{Claiming, Pointing}, + naked_subsets::{NakedPair, NakedSubset}, + singles::{HiddenSingle, NakedSingle}, + }, Grid, }; use tauri::State; @@ -32,6 +32,8 @@ fn main() { get_difficulty, set_marking_assist, get_marking_assist, + set_begin_with_marks, + get_begin_with_marks, get_hint ]) .run(tauri::generate_context!()) @@ -42,13 +44,15 @@ fn main() { struct Settings { difficulty: u8, marking_assist: bool, + begin_with_marks: bool, } impl Default for Settings { fn default() -> Self { Self { - difficulty: 2, - marking_assist: false, + difficulty: 1, + marking_assist: true, + begin_with_marks: false, } } } @@ -100,6 +104,16 @@ fn get_marking_assist(settings: State<'_, SettingsState>) -> Result { Ok(settings.0.lock().unwrap().marking_assist) } +#[tauri::command] +fn set_begin_with_marks(begin_with_marks: bool, settings: State<'_, SettingsState>) { + settings.0.lock().unwrap().begin_with_marks = begin_with_marks; +} + +#[tauri::command] +fn get_begin_with_marks(settings: State<'_, SettingsState>) -> Result { + Ok(settings.0.lock().unwrap().begin_with_marks) +} + #[derive(Serialize)] pub enum GetHintResult { Success, @@ -120,19 +134,22 @@ fn get_hint(grid: [[i8; 9]; 9], candidates: [[[bool; 10]; 9]; 9]) -> (Option