h48

A prototype for an optimal Rubik's cube solver, work in progress.
git clone https://git.tronto.net/h48
Download | Log | Files | Refs | README | LICENSE

commit 5bdf6e73179cf944ce82606545beb0b0d63a59a3
parent e2004826a56b1e2cac8b3d5a94535e480ca2855d
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Wed,  8 Nov 2023 18:53:00 +0100

Improved comments

Diffstat:
MTODO.txt | 25+++++++------------------
Mcube.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++----------
Mcube.h | 43+++++++++++++++++++++++++++++++++++++++----
3 files changed, 95 insertions(+), 32 deletions(-)

diff --git a/TODO.txt b/TODO.txt @@ -1,11 +1,12 @@ -## Generic solver +## Solving + +### Generic solver -* finish implementation * tests: solve full cube (max 7-8 moves?) * more tests: eo and other stuff * benchmarks -## Coordinates +### Coordinates * [done] eo * co @@ -17,22 +18,10 @@ What about symcoord? -## More I/O - - -## Solving +### More solvers -All solving functions take a cube and some parameters as input. - -* Depth [uint, <= 20]: all solvers work at fixed depth. The caller - implementation can implement an A* search. -* max [int]: the maximum number of solutions to find. Set to a negative - value for all solutions. -* sol [move_t *]: the array for returning the solutions. The caller - should make sure that it can hold at least max * depth values. -* Table [uint8_t *]: table with all the necessare pre-computed info. - The table can be generated with a companion function, but reading - from and writing to file is delegated to the caller implementation. +* solve_light: first based on solve_generic, then optimize; benchmark + to see up to what length it works best (7 moves? 10 moves?) ### Implement the following solvers: diff --git a/cube.c b/cube.c @@ -3688,19 +3688,51 @@ implementation of all the solving algorithms. typedef struct { cube_t cube; - int (*estimate)(cube_t); uint8_t depth; int maxsols; move_t *sols; int nsols; int nmoves; move_t moves[20]; + int (*estimate)(cube_t); } dfs_arg_t; -int +static bool +allowednextmove(dfs_arg_t arg, move_t m) +{ + int n; + move_t mbase, l1base, l2base, maxis, l1axis, l2axis; + + n = arg.nmoves; + + if (n == 0) + return true; + + mbase = m / 3; + maxis = mbase / 2; + l1base = arg.moves[n-1] / 3; + l1axis = l1base / 2; + + if (mbase == l1base || (maxis == l1axis && mbase < l1base)) + return false; + + if (n == 1) + return true; + + l2base = arg.moves[n-2] / 3; + l2axis = l1base / 2; + + return l1axis != l2axis || mbase != l2base; +} + +static int solve_generic_dfs(dfs_arg_t arg) { - int bound = arg.estimate(arg.cube); + dfs_arg_t nextarg; + int bound, ret; + move_t m; + + bound = arg.estimate(arg.cube); if (arg.nsols == arg.maxsols || bound + arg.nmoves > arg.depth) return 0; @@ -3714,36 +3746,43 @@ solve_generic_dfs(dfs_arg_t arg) return 1; } - /* TODO: loop over moves and recur */ - return 0; + memcpy(&nextarg, &arg, sizeof(dfs_arg_t)); + nextarg.nmoves = arg.nmoves + 1; + for (m = 0, ret = 0; m < 18; m++) { + if (allowednextmove(arg, m)) { + nextarg.cube = move(arg.cube, m); + nextarg.moves[arg.nmoves] = m; + ret += solve_generic_dfs(nextarg); + } + } + + return ret; } int solve_generic( cube_t cube, - int (*estimate)(cube_t), uint8_t depth, int maxsols, move_t *sols + int (*estimate)(cube_t), ) { dfs_arg_t arg; - if (!issolvable(cube) || depth > 20) + if (!issolvable(cube) || depth > 20 || estimate == NULL) return -1; arg = (dfs_arg_t) { .cube = cube, - .estimate = estimate, .depth = depth, .maxsols = maxsols, .sols = sols, .nsols = 0, .nmoves = 0, .moves = {0} + .estimate = estimate, }; return solve_generic_dfs(arg); - - return 0; } diff --git a/cube.h b/cube.h @@ -169,9 +169,44 @@ int16_t coord_eo(cube_t); /* Edge orientation */ /****************************************************************************** Solvers -Solvers return -1 in case of error, the number of solutions otherwise - -TODO +All solvers work at fixed depth, i.e. they will only find solutions of the +specified length. Iterating over the possible lengths, if desired, is left as +an implementation detail for the user of this library. + +The solutions are returned as a list of moves, which can then be converted to +a string using writemoves(). + +Unless specified otherwise, all the solutions are not trivially simplifiable. +This means that sequences like U U2 or R L R will not appear in any solution. +Moreover, two consecutive parallel moves are always going to be sorted in +increasing order. For example, L R2 may never appear in a solution, but R2 L +could. + +Solvers return -1 in case of error, the number of solutions found otherwise. + +TODO NISS / INVERSE / LINEAR as a mask? + +All solvers take at least the following parameters, satisfying the conditions +in square brackets: + - cube_t cube [issolvable(cube)]: The cube to solve. + - uint8_t depth [depth <= 20]: The lenght of the solution. + - int maxsols: The maximum number of solutions to find. The solver + stops when the limit is reached. If set to a negative number, all + the solutions are found. + - move_t *ret: The array where the moves of the solutions are stored. + There is no separator between different solutions; to read the + solutions, use the fact that all solutions has the same length: the + i-th move of the j-th solution is ret[j*depth + i]. + +Some solvers take other parameters. See below for details. ******************************************************************************/ -int solve_generic(cube_t, int (*)(cube_t), uint8_t, int, move_t *); +int solve_generic( + cube_t cube, + uint8_t depth, + int maxsols, + move_t *ret + int (*estimate)(cube_t), +); + +int solve_light(cube_t, int