Skip to content

Commit

Permalink
feat: state generators (#10)
Browse files Browse the repository at this point in the history
* refactor: generators

* wip: refactor index & cross solved gen

* feat: cfop state gen & scrmable module

* fix: scramble

* feat: more generators

* fix: formatting

* update: re-use solver in scramble

* update: README.md
  • Loading branch information
luckasRanarison authored Nov 7, 2023
1 parent 8d85630 commit e1851fb
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 202 deletions.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ See https://docs.rs/kewb/latest/kewb/ for an exhaustive list of APIs provided by
The solver needs some precomputed data which is represented by the `DataTable` struct. However, generating it takes some amount of time so it's recommended to write it on the disk or bundle it with the executable. You can use the `write_table()` function or the `table` command from `kewb-cli` to generate the table.

```rust
use kewb::{error::Error, utils::scramble_from_string, DataTable, FaceCube, Solver, CubieCube};

use kewb::{
error::Error,
generators::generate_state_cross_solved,
scramble::{scramble_from_state, scramble_from_str},
CubieCube, DataTable, FaceCube, Solver,
};

fn main() -> Result<(), Error> {
// Method 1: Bundling the table in the executable
Expand All @@ -31,19 +35,28 @@ fn main() -> Result<(), Error> {
let table = DataTable::default();

let mut solver = Solver::new(&table, 23, None);
let scramble = scramble_from_string("R U R' U'").unwrap(); // vec![R, U, R3, U3]
let scramble = scramble_from_str("R U R' U'")?; // vec![R, U, R3, U3]
let state = CubieCube::from(&scramble);
let solution = solver.solve(state).unwrap();

println!("{}", solution);

solver.clear();

let faces = "DRBLUURLDRBLRRBFLFFUBFFDRUDURRBDFBBULDUDLUDLBUFFDBFLRL";
let face_cube = FaceCube::try_from(faces)?;
let state = CubieCube::try_from(&face_cube)?;
let solution = solver.solve(state).unwrap();

println!("{}", solution);

solver.clear();

let cross_solved = generate_state_cross_solved();
let scramble = scramble_from_state(edges_solved, &mut solver)?;

println!("{:?}", scramble);

Ok(())
}
```
Expand All @@ -58,9 +71,10 @@ kewb-cli help
kewb-cli solve --scramble "R U R' U'" --max 22 --timeout 1 --details
kewb-cli solve -s "R U R' U'" -m 22 -t 1 -d
kewb-cli solve --facelet DRBLUURLDRBLRRBFLFFUBFFDRUDURRBDFBBULDUDLUDLBUFFDBFLRL
# default values: number = 1, preview = false
# default values: state = random, number = 1, preview = false
kewb-cli scramble -p
kewb-cli scramble -n 5
kewb-cli scramble f2l-solved
# generates the table used by the solver
kewb-cli table ./path_to_file
```
Expand Down
59 changes: 42 additions & 17 deletions kewb-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::{arg, command, Parser, Subcommand};
use clap::{arg, command, Parser, Subcommand, ValueEnum};
use crossterm::{
cursor::{MoveLeft, MoveRight, MoveUp},
execute,
Expand All @@ -7,10 +7,11 @@ use crossterm::{
use kewb::{
error::Error,
fs::{decode_table, write_table},
utils::{generate_random_state, scramble_from_string},
generators::*,
scramble::{scramble_from_state, scramble_from_str},
Color,
};
use kewb::{CubieCube, FaceCube, Move, Solver};
use kewb::{CubieCube, FaceCube, Solver};
use spinners::Spinner;
use std::{
io::{self, stdout},
Expand Down Expand Up @@ -54,6 +55,9 @@ enum Commands {

#[command(about = "generates scramble")]
Scramble {
#[arg(default_value = "random")]
state: State,

#[arg(short, long, default_value_t = 1)]
number: usize,

Expand All @@ -65,6 +69,17 @@ enum Commands {
Table { path: String },
}

#[derive(ValueEnum, Clone)]
enum State {
Random,
CrossSolved,
F2LSolved,
OllSolved,
OllCrossSolved,
EdgesSolved,
CornersSolved,
}

fn solve(
scramble: &Option<String>,
facelet: &Option<String>,
Expand Down Expand Up @@ -124,12 +139,10 @@ fn solve_scramble(
timeout: Option<f32>,
details: bool,
) -> Result<(), Error> {
if let Some(scramble) = scramble_from_string(scramble) {
let state = CubieCube::from(&scramble);
Ok(solve_state(state, max, timeout, details)?)
} else {
Err(Error::InvalidScramble)
}
let scramble = scramble_from_str(scramble)?;
let state = CubieCube::from(&scramble);

solve_state(state, max, timeout, details)
}

fn solve_facelet(facelet: &str, max: u8, timeout: Option<f32>, details: bool) -> Result<(), Error> {
Expand Down Expand Up @@ -196,21 +209,29 @@ fn print_facelet(facelet: &FaceCube) -> Result<(), io::Error> {
Ok(())
}

fn scramble(number: usize, preview: bool) -> Result<(), Error> {
fn scramble(state: &State, number: usize, preview: bool) -> Result<(), Error> {
let table = decode_table(TABLE)?;
let start = Instant::now();
let mut spinner = Spinner::new(spinners::Spinners::Dots, "Generating scramble".to_owned());
let mut solver = Solver::new(&table, 25, None);
let mut scrambles = Vec::new();
let mut states = Vec::new();
let table = decode_table(TABLE)?;
let start = Instant::now();

for _ in 0..number {
let mut solver = Solver::new(&table, 25, None);
let state = generate_random_state();
let scramble = solver.solve(state).unwrap().get_all_moves();
let scramble: Vec<Move> = scramble.iter().rev().map(|m| m.get_inverse()).collect();
let state = match state {
State::Random => generate_random_state(),
State::CrossSolved => generate_state_cross_solved(),
State::F2LSolved => generate_state_f2l_solved(),
State::OllSolved => generate_state_oll_solved(),
State::OllCrossSolved => generate_state_oll_cross_solved(),
State::EdgesSolved => generate_state_edges_solved(),
State::CornersSolved => generate_state_corners_solved(),
};
let scramble = scramble_from_state(state, &mut solver)?;

states.push(state);
scrambles.push(scramble);
solver.clear();
}

let end = Instant::now();
Expand Down Expand Up @@ -267,7 +288,11 @@ fn main() {
timeout,
details,
}) => solve(scramble, facelet, *max, *timeout, *details),
Some(Commands::Scramble { number, preview }) => scramble(*number, *preview),
Some(Commands::Scramble {
state,
number,
preview,
}) => scramble(state, *number, *preview),
Some(Commands::Table { path }) => table(path),
_ => Ok(()),
};
Expand Down
143 changes: 143 additions & 0 deletions kewb/src/cube/generators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::constants::{CO_COUNT, CP_COUNT, EO_COUNT, EP_COUNT};

use super::{cubie::CubieCube, index::*};
use rand::{rngs::ThreadRng, seq::SliceRandom, thread_rng, Rng};

/// Randomly swaps corner or edges to fix parity.
fn fix_parity(state: &mut CubieCube, rng: &mut ThreadRng, corners: Vec<usize>, edges: Vec<usize>) {
if rng.gen_bool(0.5) {
swap_edges(state, rng, edges)
} else {
swap_corners(state, rng, corners)
}
}

fn swap_edges(state: &mut CubieCube, rng: &mut ThreadRng, edges: Vec<usize>) {
let pos: Vec<&usize> = edges.choose_multiple(rng, 2).collect();
let a = *pos[0];
let b = *pos[1];
state.ep.swap(a, b)
}

fn swap_corners(state: &mut CubieCube, rng: &mut ThreadRng, corners: Vec<usize>) {
let pos: Vec<&usize> = corners.choose_multiple(rng, 2).collect();
let a = *pos[0];
let b = *pos[1];
state.cp.swap(a, b)
}

/// Generates a random state with corners solved.
pub fn generate_state_corners_solved() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
ep: index_to_ep(rng.gen_range(0..EP_COUNT)),
eo: index_to_eo(rng.gen_range(0..EO_COUNT)),
..Default::default()
};

if !state.is_solvable() {
swap_edges(&mut state, &mut rng, (0..12).collect());
}

state
}

/// Generates a random state with edges solved.
pub fn generate_state_edges_solved() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
cp: index_to_cp(rng.gen_range(0..CP_COUNT)),
co: index_to_co(rng.gen_range(0..CO_COUNT)),
..Default::default()
};

if !state.is_solvable() {
swap_corners(&mut state, &mut rng, (0..8).collect());
}

state
}

/// Generates a random state with oriented solved last layer cross.
pub fn generate_state_oll_cross_solved() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
cp: index_to_cp_f2l(rng.gen_range(0..4)),
co: index_to_co_f2l(rng.gen_range(0..27)),
ep: index_to_ep_f2l(rng.gen_range(0..24)),
..Default::default()
};

if !state.is_solvable() {
fix_parity(&mut state, &mut rng, (0..4).collect(), (4..8).collect())
}

state
}

/// Generates a random state with oriented last layer corners and edges.
pub fn generate_state_oll_solved() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
cp: index_to_cp_f2l(rng.gen_range(0..4)),
ep: index_to_ep_f2l(rng.gen_range(0..24)),
..Default::default()
};

if !state.is_solvable() {
fix_parity(&mut state, &mut rng, (0..4).collect(), (4..8).collect())
}

state
}

/// Generates a random state with solved First two layer.
pub fn generate_state_f2l_solved() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
cp: index_to_cp_f2l(rng.gen_range(0..4)),
co: index_to_co_f2l(rng.gen_range(0..27)),
ep: index_to_ep_f2l(rng.gen_range(0..24)),
eo: index_to_eo_f2l(rng.gen_range(0..8)),
};

if !state.is_solvable() {
fix_parity(&mut state, &mut rng, (0..4).collect(), (4..8).collect())
}

state
}

/// Generates a random state with solved cross.
pub fn generate_state_cross_solved() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
cp: index_to_cp(rng.gen_range(0..CP_COUNT)),
co: index_to_co(rng.gen_range(0..CO_COUNT)),
ep: index_to_ep_cross(rng.gen_range(0..40320)),
eo: index_to_eo_cross(rng.gen_range(0..128)),
};

if !state.is_solvable() {
fix_parity(&mut state, &mut rng, (0..8).collect(), (0..8).collect())
}

state
}

/// Generates a random state on the cubie level.
pub fn generate_random_state() -> CubieCube {
let mut rng = thread_rng();
let mut state = CubieCube {
cp: index_to_cp(rng.gen_range(0..CP_COUNT)),
co: index_to_co(rng.gen_range(0..CO_COUNT)),
ep: index_to_ep(rng.gen_range(0..EP_COUNT)),
eo: index_to_eo(rng.gen_range(0..EO_COUNT)),
};

if !state.is_solvable() {
fix_parity(&mut state, &mut rng, (0..8).collect(), (0..12).collect())
}

state
}
Loading

0 comments on commit e1851fb

Please sign in to comment.