Skip to content

Commit

Permalink
Add Skewb scrambles (#58)
Browse files Browse the repository at this point in the history
See the comment in `src/rs/scramble/puzzles/skewb.rs` (at
https://github.com/cubing/twsearch/pull/58/files#diff-4633898083e67a49d43cbe5d1d3f6c5f84b7be5bf69bc887a39224238635f433)
for information about the approach.

Co-authored-by: Jeremy Fleischman <[email protected]>
  • Loading branch information
lgarron and jfly authored Sep 14, 2024
2 parents ba4c581 + a703c59 commit fb77098
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/rs/_internal/search/idf_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ impl IDFSearch {
}
}

assert!(individual_search_options.get_max_depth() >= individual_search_options.get_min_depth());

let (solution_sender, search_solutions) = SearchSolutions::construct();
let mut individual_search_data = IndividualSearchData {
individual_search_options,
Expand Down
1 change: 1 addition & 0 deletions src/rs/examples/random_scramble_for_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn main() {
Event::Cube5x5x5Blindfolded,
Event::Cube3x3x3MultiBlind,
Event::ClockSpeedsolving,
Event::SkewbSpeedsolving,
Event::Cube3x3x3FewestMoves,
Event::Cube3x3x3FewestMoves,
Event::Cube3x3x3FewestMoves,
Expand Down
2 changes: 1 addition & 1 deletion src/rs/scramble/puzzles/cube2x2x2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn scramble_2x2x2() -> Alg {
OrbitOrientationConstraint::OrientationsMustSumToZero,
);
let generators = generators_from_vec_str(vec!["U", "L", "F", "R"]);
if let Some(scramble) = filtered_search(&scramble_pattern, generators, Some(4), Some(11)) {
if let Some(scramble) = filtered_search(&scramble_pattern, generators, 4, Some(11)) {
return scramble;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/rs/scramble/puzzles/definitions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ kpuzzle_from_json_file!(pub(crate), cube5x5x5, "5x5x5.kpuzzle.json");
kpuzzle_from_json_file!(pub(crate), cube6x6x6, "6x6x6.kpuzzle.json");
kpuzzle_from_json_file!(pub(crate), cube7x7x7, "7x7x7.kpuzzle.json");
kpuzzle_from_json_file!(pub(crate), tetraminx, "tetraminx.kpuzzle.json");
kpuzzle_from_json_file!(pub(crate), skewb_fixed_corner_with_co_tweaks, "skewb-fixed-corner-with-co-tweaks.kpuzzle.json");
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"name": "skewb",
"orbits": [
{
"orbitName": "CORNERS1",
"numPieces": 3,
"numOrientations": 3
},
{
"orbitName": "CORNERS2",
"numPieces": 4,
"numOrientations": 3
},
{
"orbitName": "CENTERS",
"numPieces": 6,
"numOrientations": 1
}
],
"defaultPattern": {
"CORNERS1": {
"pieces": [0, 1, 2],
"orientation": [0, 0, 0],
"orientationMod": [1, 0, 0]
},
"CORNERS2": {
"pieces": [0, 1, 2, 3],
"orientation": [0, 0, 0, 0],
"orientationMod": [1, 0, 0, 0]
},
"CENTERS": {
"pieces": [0, 1, 2, 3, 4, 5],
"orientation": [0, 0, 0, 0, 0, 0]
}
},
"moves": {
"U": {
"CORNERS1": {
"permutation": [0, 1, 2],
"orientationDelta": [0, 0, 1]
},
"CORNERS2": {
"permutation": [1, 3, 2, 0],
"orientationDelta": [2, 2, 0, 2]
},
"CENTERS": {
"permutation": [0, 1, 2, 5, 3, 4],
"orientationDelta": [0, 0, 0, 0, 0, 0]
}
},
"L": {
"CORNERS1": {
"permutation": [0, 1, 2],
"orientationDelta": [1, 0, 0]
},
"CORNERS2": {
"permutation": [3, 1, 0, 2],
"orientationDelta": [2, 0, 2, 2]
},
"CENTERS": {
"permutation": [4, 1, 0, 3, 2, 5],
"orientationDelta": [0, 0, 0, 0, 0, 0]
}
},
"R": {
"CORNERS1": {
"permutation": [0, 1, 2],
"orientationDelta": [0, 1, 0]
},
"CORNERS2": {
"permutation": [0, 2, 3, 1],
"orientationDelta": [0, 2, 2, 2]
},
"CENTERS": {
"permutation": [0, 2, 5, 3, 4, 1],
"orientationDelta": [0, 0, 0, 0, 0, 0]
}
},
"B": {
"CORNERS1": {
"permutation": [2, 0, 1],
"orientationDelta": [2, 2, 2]
},
"CORNERS2": {
"permutation": [0, 1, 2, 3],
"orientationDelta": [0, 0, 0, 1]
},
"CENTERS": {
"permutation": [0, 1, 4, 3, 5, 2],
"orientationDelta": [0, 0, 0, 0, 0, 0]
}
}
}
}
1 change: 1 addition & 0 deletions src/rs/scramble/puzzles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod cube2x2x2;
pub mod cube3x3x3;
pub mod megaminx;
pub mod pyraminx;
pub mod skewb;

mod definitions;
mod static_move_list;
2 changes: 1 addition & 1 deletion src/rs/scramble/puzzles/pyraminx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn scramble_pyraminx() -> Alg {

let mut rng = thread_rng();
let generators = generators_from_vec_str(vec!["U", "L", "R", "B"]); // TODO: cache
if let Some(scramble) = filtered_search(&scramble_pattern, generators, Some(4), Some(11)) {
if let Some(scramble) = filtered_search(&scramble_pattern, generators, 4, Some(11)) {
let mut alg_nodes: Vec<AlgNode> = vec![];
for tip_move in tip_moves {
let amount = rng.gen_range(-1..=1);
Expand Down
95 changes: 95 additions & 0 deletions src/rs/scramble/puzzles/skewb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use cubing::alg::Alg;

use super::{
super::randomize::{
randomize_orbit_naïve, OrbitOrientationConstraint, OrbitPermutationConstraint,
},
super::scramble_search::{filtered_search, generators_from_vec_str},
definitions::skewb_fixed_corner_with_co_tweaks_kpuzzle,
};

pub fn scramble_skewb() -> Alg {
let kpuzzle = skewb_fixed_corner_with_co_tweaks_kpuzzle();
loop {
let mut scramble_pattern = kpuzzle.default_pattern();

/* The total orientation of each corner orbit is constrained by the permutation of the other.
* That is, suppose we have a valid state of Skewb with values labelled as follows:
*
* (Take note of the values highlighted by ↓↓ and ↑↑.)
*
* ↓↓
* ↓↓
* {
* "CORNERS1": { "pieces": [@2, @2, @2], "orientation": [#1, @1, @1] },
* "CORNERS2": { "pieces": [@1, @1, @1, @1], "orientation": [#2, @2, @2, @2]},
* "CENTERS": { … }
* } ↑↑
* ↑↑
*
* Then:
*
* - The orientation of value `#1` is determined by the values labeled `@1`.
* - The orientation of value `#2` is determined by the values labeled `@2`.
*
* Now, we could either:
*
* - Do a bit of math to determine the values `#1` and `#2.`
* - Set the orientations of `#1` and `#2` to "ignored" by using the `orientationMod` feature.
*
* We choose to do the latter with respect to the solved state, then generate a random permutation of this pattern
* (taking into account permutation parity for each orbit) and solve it. In the resulting state:
*
* - All the `@1` values match the solved state, so `#1` must also match the solved state.
* - All the `@2` values match the solved state, so `#2` value also match the solved state.
*
* That is: the entire puzzle is solved, and we can use this to return a uniform random scramble (subject to other filtering).
*
* This approach does not have any performance implications, and also has the benefit that it allows us to randomize each orbit independently.
*
* The numbers check out, as this gives us the following number of distinct states:
*
* | Orbit | Calculation | Number of possibilities |
* |----------|----------------|-------------------------|
* | CORNERS1 | 4! / 2 * 3^3 | 324 |
* | CORNERS2 | 3! / 2 * 3^2 | 27 |
* | CENTERS | 6! / 2 | 360 |
* |----------|----------------|-------------------------|
* | Overall | 324 * 27 * 360 | 3149280 |
*
* This matches: https://www.jaapsch.net/puzzles/skewb.htm
*/

let orbit_info = &kpuzzle.data.ordered_orbit_info[0];
assert_eq!(orbit_info.name.0, "CORNERS1");
randomize_orbit_naïve(
&mut scramble_pattern,
orbit_info,
OrbitPermutationConstraint::SingleOrbitEvenParity,
OrbitOrientationConstraint::SetPieceZeroToIgnoredOrientation,
);

let orbit_info = &kpuzzle.data.ordered_orbit_info[1];
assert_eq!(orbit_info.name.0, "CORNERS2");
randomize_orbit_naïve(
&mut scramble_pattern,
orbit_info,
OrbitPermutationConstraint::SingleOrbitEvenParity,
OrbitOrientationConstraint::SetPieceZeroToIgnoredOrientation,
);

let orbit_info = &kpuzzle.data.ordered_orbit_info[2];
assert_eq!(orbit_info.name.0, "CENTERS");
randomize_orbit_naïve(
&mut scramble_pattern,
orbit_info,
OrbitPermutationConstraint::SingleOrbitEvenParity,
OrbitOrientationConstraint::OrientationsMustSumToZero,
);

let generators = generators_from_vec_str(vec!["U", "L", "R", "B"]); // TODO: cache
if let Some(scramble) = filtered_search(&scramble_pattern, generators, 7, Some(11)) {
return scramble;
}
}
}
3 changes: 2 additions & 1 deletion src/rs/scramble/random_scramble_for_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::{
cube3x3x3::{scramble_3x3x3, scramble_3x3x3_bld, scramble_3x3x3_fmc},
megaminx::scramble_megaminx,
pyraminx::scramble_pyraminx,
skewb::scramble_skewb,
},
Event,
};
Expand All @@ -31,7 +32,7 @@ pub fn random_scramble_for_event(event: Event) -> Result<Alg, PuzzleError> {
Event::ClockSpeedsolving => Ok(scramble_clock()),
Event::MegaminxSpeedsolving => Ok(scramble_megaminx()),
Event::PyraminxSpeedsolving => Ok(scramble_pyraminx()),
Event::SkewbSpeedsolving => err,
Event::SkewbSpeedsolving => Ok(scramble_skewb()),
Event::Square1Speedsolving => err,
Event::Cube4x4x4Blindfolded => err,
Event::Cube5x5x5Blindfolded => Ok(scramble_5x5x5_bld()),
Expand Down
28 changes: 19 additions & 9 deletions src/rs/scramble/randomize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ impl Default for OrbitPermutationConstraint {
pub(crate) enum OrbitOrientationConstraint {
AnySum,
OrientationsMustSumToZero,
SetPieceZeroToIgnoredOrientation, // Note: this refers to the piece that is at index 0 in the *solved* pattern (i.e. the piece with value `0` in the `permutation` array), which may not necessarily be at index 0 in the *randomized* pattern.
}

// Selects a random permutation (ignoring parity).
Expand Down Expand Up @@ -48,28 +49,37 @@ pub(crate) fn randomize_orbit_naïve(
for (i, p) in piece_order.iter().enumerate() {
let i = i as u8;
pattern.set_piece(orbit_info, i, *p);
let orientation = match (i == orbit_info.num_pieces - 1, &orientation_constraints) {
(true, OrbitOrientationConstraint::OrientationsMustSumToZero) => {
subtract_u8_mod(0, total_orientation, orbit_info.num_orientations)
let orientation_with_mod = match (&orientation_constraints, i == orbit_info.num_pieces - 1, *p == 0) {
(OrbitOrientationConstraint::OrientationsMustSumToZero, true, _) => {
OrientationWithMod {
orientation: subtract_u8_mod(0, total_orientation, orbit_info.num_orientations),
orientation_mod: 0,
}
}
(_, _) => {
(OrbitOrientationConstraint::SetPieceZeroToIgnoredOrientation, _, true) => {
OrientationWithMod {
orientation: 0,
orientation_mod: 1,
}
}
(_, _, _) => {
let random_orientation = rng.gen_range(0..orbit_info.num_orientations);
total_orientation = add_u8_mod(
total_orientation,
random_orientation,
orbit_info.num_orientations,
);
random_orientation
OrientationWithMod {
orientation: random_orientation,
orientation_mod: 0,
}
}
};

pattern.set_orientation_with_mod(
orbit_info,
i,
&OrientationWithMod {
orientation,
orientation_mod: 0, // TODO
},
&orientation_with_mod,
);
}
piece_order
Expand Down
6 changes: 4 additions & 2 deletions src/rs/scramble/scramble_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,19 @@ pub(crate) fn basic_idfs(
pub(crate) fn filtered_search(
scramble_pattern: &KPattern,
generators: Generators,
min_optimal_moves: Option<usize>,
min_optimal_moves: usize,
min_scramble_moves: Option<usize>,
) -> Option<Alg> {
assert_ne!(min_optimal_moves, 0);

let mut idfs = basic_idfs(scramble_pattern.kpuzzle(), generators, None);
if idfs
.search(
scramble_pattern,
IndividualSearchOptions {
min_num_solutions: Some(1),
min_depth: Some(0),
max_depth: min_optimal_moves.map(|v| v - 1),
max_depth: Some(min_optimal_moves - 1),
disallowed_initial_quanta: None,
disallowed_final_quanta: None,
},
Expand Down

0 comments on commit fb77098

Please sign in to comment.