Closed anicolao closed 2 years ago
This is an important feature, but it will probably be a few months until I can tackle it properly. This is mainly because I need to figure out how to do it with accessibility at the top of mind.
If you can use sprites for now, then I would recommend sticking with that. If that gets too gnarly, I can see about exposing different ways to set stickerings, but those would be very subject to change at any time.
Right now I am distracted implementing the new protocol for the GAN cubes (the i3, iCarry, etc) so it may be a few days or even weeks before I come back to wanting the stickering, but a few months sounds a bit long.
My app currently displays solve analyses like this:
and what I want to do long term is let the user define a DAG of solution stages for their preferred solution method, and have the solve breakdown automatically produce the table, moves, optimal move options for their actual solve, rather than having roux hardcoded.
My intention is to let the user define a mask per cubie of the stage, that contains 5 states:
Ideally the experimental stickering would take this same mask and do the following:
If there are different highlight modes for solve/place/orient, I would like control of the choices.
All this can of course be implemented with sprites, but it looks hard to do it that way.
I forgot the mask also needs a mask premove that lets the user apply a fake turn or orientation prior to testing the mask. e.g. for a roux solve where SB is inverted, the premove would be R2, enabling white/yellow to both be on the bottom, say white on the fb and yellow on the sb.
So, we used to have a way to set a completely custom stickering for a twisty player, but no one was using it. How helpful would it be if you could pass in your own PuzzleAppearance
as JSON?
For example, L6E in standard orientation would be:
{
"orbits": {
"EDGES": {
"pieces": [
{ "facelets": ["regular", "regular"] }, // UF
{ "facelets": ["regular", "regular"] }, // UR
{ "facelets": ["regular", "regular"] }, // UB
{ "facelets": ["regular", "regular"] }, // UL
{ "facelets": ["regular", "regular"] }, // DF
{ "facelets": ["dim", "dim"] }, // DR
{ "facelets": ["regular", "regular"] }, // DB
{ "facelets": ["dim", "dim"] }, // DL
{ "facelets": ["dim", "dim"] }, // FR
{ "facelets": ["dim", "dim"] }, // FL
{ "facelets": ["dim", "dim"] }, // BR
{ "facelets": ["dim", "dim"] } // BL
]
},
"CORNERS": {
"pieces": [
{ "facelets": ["dim", "dim", "dim"] }, // UFR
{ "facelets": ["dim", "dim", "dim"] }, // URB
{ "facelets": ["dim", "dim", "dim"] }, // UBL
{ "facelets": ["dim", "dim", "dim"] }, // ULF
{ "facelets": ["dim", "dim", "dim"] }, // DRF
{ "facelets": ["dim", "dim", "dim"] }, // DFL
{ "facelets": ["dim", "dim", "dim"] }, // DLB
{ "facelets": ["dim", "dim", "dim"] } // DBR
]
},
"CENTERS": {
"pieces": [
{ "facelets": ["regular"] }, // U
{ "facelets": ["dim"] }, // L
{ "facelets": ["regular"] }, // F
{ "facelets": ["dim"] }, // R
{ "facelets": ["regular"] }, // B
{ "facelets": ["regular"] } // D
]
}
}
}
You'd set it in JS using player.experimentalAppearance = /* JSON */
.
You can see similar definitions by running await puzzles.cube3x3x3.appearance("L6E")
for other stickerings, but note that those will return 5 facelet entries per piece rather than 1 to 3 as in the example above. (This is because the pieces definitions are shared internally with Megaminx.). It's safe to truncate down to 3 entries per corner, etc.
This might change over time, but I'm fairly confident the core approach will stay forward-compatible. Your examples would correspond to:
- locked: this cubie is supposed to be already solved before the stage starts
["dim", "dim", "dim"]
- solve it: this cubie is supposed to be solved by this stage
["regular", "regular", "regular"]
- place it: this cubie is supposed to be in position (but not oriented) by this stage
For now, this would also have to be ["dim", "dim", "dim"]
or ["regular", "regular", "regular"]
- orient it: this cubie is supposed to be oriented (but may not be in position) by this stage
["regular", "ignored", "ignored"]
["oriented", "ignored", "ignored"]
"oriented"
is currently turquoise, but we've already experimented with axis-specific colors and I'd be totally fine with adding an experimental config to set that to another color (only solid colors for now).
- don't care: this cubie doesn't matter to this stage
["ignored", "ignored", "ignored"]
https://www.stefan-pochmann.info/hume/hume_diploma_thesis.pdf
and what I want to do long term is let the user define a DAG of solution stages for their preferred solution method, and have the solve breakdown automatically produce the table, moves, optimal move options for their actual solve, rather than having roux hardcoded.
This is very much something I also want to support, given that there hasn't been tremendously much progress since Hume. Definitely interested in prioritizing work for that.
I forgot the mask also needs a mask premove that lets the user apply a fake turn or orientation prior to testing the mask. e.g. for a roux solve where SB is inverted, the premove would be R2, enabling white/yellow to both be on the bottom, say white on the fb and yellow on the sb.
Would this work?
player.experimentalAppearancePreAlg = "R2";
You'd set it in JS using
player.experimentalAppearance = /* JSON */
.
Okay, it looks like this approach is as straightforward as I was hoping: https://github.com/cubing/cubing.js/commit/666c83d15bc7f8fd158bf3386ba04e00c0fdf1c9
I'll give this a try - it looks like it should be relatively easy to translate my masks into this format.
The rotation premove might not be needed, I could apply it to the mask instead and then highlight the right facelets.
It may be a day or two before I get to this as I am currently trying to get orientation working with the i3/iCarry/MonsterGo/etc and I'm not fond of reverse engineering so I am dragging my feet a bit.
We should start a new issue on the solve dag question. I currently do not plan to use cubing.js for this at all, I am using a fork of onionhoney's trainer that already has a lot of the functionality I need built into it. I had originally selected cubing.js because it is obvious that this is a lifelong hobby for the two of you, and therefore it was more likely to stay maintained and working than forking onionhoney's code, but I will soon be at a crossroads where I am debating excising the dependency because I'm using less and less of it; so if we can sort out the solution dag in a way that works for both of us in a new issue, that'd be a reason to carry the dependency.
I couldn't figure out how to use this. The commit doesn't seem to be anywhere I can get at it, and when I tried to do this in my client code I couldn't even figure out where to come by the PG3D type.
const pg3d = await twistyPlayer.experimentalCurrentThreeJSPuzzleObject() as PG3D;
which I was trying to do because it looks like PG3D directly has the set appearance method I need on it but it doesn't seem to be exported anywhere.
I was able to get the following awful hack to "work":
const pg3d = (await twistyPlayer.experimentalCurrentThreeJSPuzzleObject()) as any;
if (pg3d.setAppearance) {
console.log("success! found the method");
pg3d.setAppearance(json);
} else {
console.log("FAIL: no set appearance method");
}
but only if I didn't use PG3D
because the experimentalSetAppearance
method throws an exception. It most likely doesn't work in your patch either I am guessing.
On the upside, this is enough for me to experiment with setting the stickering and seeing if it looks OK. If it does, is it feasible to get this added to main
?
OK I can confirm that JSON like this is flexible enough for me, but I really prefer the PG3D cube model rather than the usual one. I'll watch for a patch to land to make use of it for real and live with the odd looking cube visualization for a bit to see how the stickering works for me in practice.
I've been working on a new prop today that I'm hoping will serve this use case:
I'm still writing up documentation, but basically it now supports a string with a single character per piece:
-
D
@
(currently shows the same as -
, hopefully we can distinguish it in the future)?
I
The JSON stickering mask (new name for "appearance") can also be set using player.experimentalStickeringMaskOrbits = /* JSON */
.
You should be able to try it on the latest main
.
I ran into a new problem that I'm not sure how best to solve. The meanings of UFR, UDR, etc are not easily mapped to the cubie indices. Though your comments suggest that for example a particular index is UFR, what we really mean here is that index would be UFR if white was up and green is forward. However when the user has reoriented the cube all the facelets are in different positions. So for now I disabled the stickering and will sleep on how and when to do the mapping from the cubies names (UFR etc) which I know what I want the stickering to be to the cubies indices, which vary according to how the user has rotated the cube. I should be able to fix it with a fairly straightforward changeBasis at the start.
The string you're asking me to construct is fine, but as I'll be accessing this API from javascript if there's an option where I can pass the information as an array that would be nice. Not a big deal as I can just .join('')
on my array to build the strings.
I assume the order of the cubies in the string is the same as in the JSON. It's not the same as my internal representation so I'll have to map from that through the rotation to the final order for twisty player.
The meanings of UFR, UDR, etc are not easily mapped to the cubie indices. Though your comments suggest that for example a particular index is UFR, what we really mean here is that index would be UFR if white was up and green is forward. However when the user has reoriented the cube all the facelets are in different positions
That sounds like you could actually use something like . experimentalAppearancePreAlg
after all?
So for now I disabled the stickering and will sleep on how and when to do the mapping from the cubies names (UFR etc) which I know what I want the stickering to be to the cubies indices, which vary according to how the user has rotated the cube. I should be able to fix it with a fairly straightforward changeBasis at the start.
If you have a KTransformation
, it should be fairly easy to apply to a stickering mask. Would you like some sample code for that?
I assume the order of the cubies in the string is the same as in the JSON. It's not the same as my internal representation so I'll have to map from that through the rotation to the final order for twisty player.
Yes, cubing.js
always uses Reid order for 3x3x3 pieces.
If I could specify a rotation in experimentalAppearancePreAlg
and have that only affect the stickering, that would be very convenient for me. Though I didn't quite grok your Ktransformation
comment, so perhaps you want to share sample code to illustrate that first ... which way do you think will be generally preferable for others also using these APIs?
I can't get it to work in my application. Even when depending on my local sync of cubing.js, I get a typescript error for the following typescript:
twistyPlayer.experimentalStickeringMaskOrbits =
'EDGES:DD----DDD--D,CORNERS:I-----I-,CENTERS:DDDDDD';
the error is:
Property 'experimentalStickeringMaskOrbits' does not exist on type 'TwistyPlayer'. Did you mean 'experimentalStickering'?ts(2551)
which suggests to me that I haven't successfully depended on the right version of cubing.js, though all I did was
npm remove cubing
npm install ../cubing.js
where ../cubing.js
contains a clone of cubing.js.
Looks like the missing magic was make build
in the cloned cubing.js
.
I can't for the life of me figure out where there is an example of the new intended JSON. Can you add a pointer to a test or example that uses the JSON interface?
I can't for the life of me figure out where there is an example of the new intended JSON. Can you add a pointer to a test or example that uses the JSON interface?
Ah, I'm sorry that wasn't clear. It's the exact same format as in this comment: https://github.com/cubing/cubing.js/issues/224#issuecomment-1260511101 You should be able to paste that example directly.
If I could specify a rotation in
experimentalAppearancePreAlg
and have that only affect the stickering, that would be very convenient for me. Though I didn't quite grok yourKtransformation
comment, so perhaps you want to share sample code to illustrate that first ... which way do you think will be generally preferable for others also using these APIs?
I'm guessing the former would be preferable, but the latter is directly usable:
import { cube3x3x3 } from "cubing/puzzles";
import { TwistyPlayer } from "cubing/twisty";
const mask = {
orbits: {
EDGES: {
pieces: [
{ facelets: ["regular", "regular"] }, // UF
{ facelets: ["regular", "regular"] }, // UR
{ facelets: ["regular", "regular"] }, // UB
{ facelets: ["regular", "regular"] }, // UL
{ facelets: ["regular", "regular"] }, // DF
{ facelets: ["dim", "dim"] }, // DR
{ facelets: ["regular", "regular"] }, // DB
{ facelets: ["dim", "dim"] }, // DL
{ facelets: ["dim", "dim"] }, // FR
{ facelets: ["dim", "dim"] }, // FL
{ facelets: ["dim", "dim"] }, // BR
{ facelets: ["dim", "dim"] }, // BL
],
},
CORNERS: {
pieces: [
{ facelets: ["dim", "dim", "dim"] }, // UFR
{ facelets: ["dim", "dim", "dim"] }, // URB
{ facelets: ["dim", "dim", "dim"] }, // UBL
{ facelets: ["dim", "dim", "dim"] }, // ULF
{ facelets: ["dim", "dim", "dim"] }, // DRF
{ facelets: ["dim", "dim", "dim"] }, // DFL
{ facelets: ["dim", "dim", "dim"] }, // DLB
{ facelets: ["dim", "dim", "dim"] }, // DBR
],
},
CENTERS: {
pieces: [
{ facelets: ["regular"] }, // U
{ facelets: ["dim"] }, // L
{ facelets: ["regular"] }, // F
{ facelets: ["dim"] }, // R
{ facelets: ["regular"] }, // B
{ facelets: ["regular"] }, // D
],
},
},
};
function transformMask(mask, transformation) {
const newMask = { orbits: {} };
for (const [orbit, orbitMask] of Object.entries(mask.orbits)) {
const newOrbitMask = { pieces: [] };
const { numPieces, numOrientations } =
transformation.kpuzzle.definition.orbits[orbit];
for (let i = 0; i < numPieces; i++) {
const perm = transformation.transformationData[orbit].permutation[i];
const ori = transformation.transformationData[orbit].orientation[i];
let facelets = orbitMask.pieces[perm].facelets.slice(0, numOrientations);
facelets = facelets.slice(ori).concat(facelets.slice(0, ori));
newOrbitMask.pieces.push({ facelets });
}
newMask.orbits[orbit] = newOrbitMask;
}
return newMask;
}
const kpuzzle = await cube3x3x3.kpuzzle();
document.body.appendChild(
new TwistyPlayer(),
).experimentalStickeringMaskOrbits = transformMask(
mask,
kpuzzle.algToTransformation("x y"),
);
EDIT: As of 2024-03-11, the code needs to be adjusted to the following:
import { cube3x3x3 } from "cubing/puzzles";
import { TwistyPlayer } from "cubing/twisty";
const mask = {
orbits: {
EDGES: {
pieces: [
{ facelets: ["regular", "regular"] }, // UF
{ facelets: ["regular", "regular"] }, // UR
{ facelets: ["regular", "regular"] }, // UB
{ facelets: ["regular", "regular"] }, // UL
{ facelets: ["regular", "regular"] }, // DF
{ facelets: ["dim", "dim"] }, // DR
{ facelets: ["regular", "regular"] }, // DB
{ facelets: ["dim", "dim"] }, // DL
{ facelets: ["dim", "dim"] }, // FR
{ facelets: ["dim", "dim"] }, // FL
{ facelets: ["dim", "dim"] }, // BR
{ facelets: ["dim", "dim"] }, // BL
],
},
CORNERS: {
pieces: [
{ facelets: ["dim", "dim", "dim"] }, // UFR
{ facelets: ["dim", "dim", "dim"] }, // URB
{ facelets: ["dim", "dim", "dim"] }, // UBL
{ facelets: ["dim", "dim", "dim"] }, // ULF
{ facelets: ["dim", "dim", "dim"] }, // DRF
{ facelets: ["dim", "dim", "dim"] }, // DFL
{ facelets: ["dim", "dim", "dim"] }, // DLB
{ facelets: ["dim", "dim", "dim"] }, // DBR
],
},
CENTERS: {
pieces: [
{ facelets: ["regular"] }, // U
{ facelets: ["dim"] }, // L
{ facelets: ["regular"] }, // F
{ facelets: ["dim"] }, // R
{ facelets: ["regular"] }, // B
{ facelets: ["regular"] }, // D
],
},
},
};
const kpuzzle = await cube3x3x3.kpuzzle();
function transformMask(mask, transformation) {
const newMask = { orbits: {} };
for (
let orbitIndex = 0;
orbitIndex < kpuzzle.definition.orbits.length;
orbitIndex++
) {
const newOrbitMask = { pieces: [] };
const { numPieces, numOrientations, orbitName } =
kpuzzle.definition.orbits[orbitIndex];
const orbitMask = mask.orbits[orbitName];
for (let i = 0; i < numPieces; i++) {
const perm = transformation.transformationData[orbitName].permutation[i];
const ori =
transformation.transformationData[orbitName].orientationDelta[i];
let facelets = orbitMask.pieces[perm].facelets.slice(0, numOrientations);
facelets = facelets.slice(ori).concat(facelets.slice(0, ori));
newOrbitMask.pieces.push({ facelets });
}
newMask.orbits[orbitName] = newOrbitMask;
}
return newMask;
}
document.body.appendChild(new TwistyPlayer()).experimentalStickeringMaskOrbits =
transformMask(mask, kpuzzle.algToTransformation("x y"));
That JSON was in fact what I had tried, but I concluded incorrectly that my JSON was wrong when I encountered
mask.ts:60 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'mask')
at getFaceletStickeringMask (mask.ts:60:32)
at PG3D.experimentalSetStickeringMask (PG3D.ts:773:41)
at Twisty3DPuzzleWrapper.ts:78:28
instead of concluding that it was broken specifically for PG3D puzzles. I had assumed that since the string format worked for PG3D, so would the JSON, and I wrote code to build the string instead.
I have now implemented this twice, once with the JSON interface and once with the string interface. Though in the string interface I chose to convert between cube representations in order to get the transform correct, overall the code seems a lot shorter and more understandable to me so I will probably go with that approach rather than the JSON. Here they are for comparison:
async function setStickersKPuzzle(mask: MaskT) {
const regular = 'regular';
const muted = 'dim';
const UF = [regular, regular];
const UR = [regular, regular];
const UB = [regular, regular];
const UL = [regular, regular];
const DF = [regular, regular];
const DR = [regular, regular];
const DB = [regular, regular];
const DL = [regular, regular];
const FR = [regular, regular];
const FL = [regular, regular];
const BR = [regular, regular];
const BL = [regular, regular];
const edgeOrder = [UF, UL, UB, UR, DF, DL, DB, DR, FL, BL, BR, FR];
const UFR = [regular, regular, regular];
const URB = [regular, regular, regular];
const UBL = [regular, regular, regular];
const ULF = [regular, regular, regular];
const DRF = [regular, regular, regular];
const DFL = [regular, regular, regular];
const DLB = [regular, regular, regular];
const DBR = [regular, regular, regular];
const cornerOrder = [ULF, UBL, URB, UFR, DFL, DLB, DBR, DRF];
const U = [regular];
const L = [regular];
const F = [regular];
const R = [regular];
const B = [regular];
const D = [regular];
const centerOrder = [U, D, F, B, L, R];
const json = {
orbits: {
EDGES: {
pieces: [
{ facelets: UF },
{ facelets: UR },
{ facelets: UB },
{ facelets: UL },
{ facelets: DF },
{ facelets: DR },
{ facelets: DB },
{ facelets: DL },
{ facelets: FR },
{ facelets: FL },
{ facelets: BR },
{ facelets: BL }
]
},
CORNERS: {
pieces: [
{ facelets: UFR },
{ facelets: URB },
{ facelets: UBL },
{ facelets: ULF },
{ facelets: DRF },
{ facelets: DFL },
{ facelets: DLB },
{ facelets: DBR }
]
},
CENTERS: {
pieces: [
{ facelets: U },
{ facelets: L },
{ facelets: F },
{ facelets: R },
{ facelets: B },
{ facelets: D }
]
}
}
};
for (let i = 0; i < edgeOrder.length; ++i) {
if (mask.ep[i] === 1) {
edgeOrder[i][0] = regular;
edgeOrder[i][1] = regular;
} else {
edgeOrder[i][0] = muted;
edgeOrder[i][1] = muted;
}
}
for (let i = 0; i < cornerOrder.length; ++i) {
if (mask.cp[i] === 1) {
cornerOrder[i][0] = regular;
cornerOrder[i][1] = regular;
cornerOrder[i][2] = regular;
} else {
cornerOrder[i][0] = muted;
cornerOrder[i][1] = muted;
cornerOrder[i][2] = muted;
}
}
for (let i = 0; i < centerOrder.length; ++i) {
if (!mask.tp || mask.tp[i] === 1) {
centerOrder[i][0] = regular;
} else {
centerOrder[i][0] = muted;
}
}
function transformMask(mask, transformation) {
const newMask = { orbits: {} };
for (const [orbit, orbitMask] of Object.entries(mask.orbits)) {
const newOrbitMask = { pieces: [] };
const { numPieces, numOrientations } = transformation.kpuzzle.definition.orbits[orbit];
for (let i = 0; i < numPieces; i++) {
const perm = transformation.transformationData[orbit].permutation[i];
const ori = transformation.transformationData[orbit].orientation[i];
let facelets = orbitMask.pieces[perm].facelets.slice(0, numOrientations);
facelets = facelets.slice(ori).concat(facelets.slice(0, ori));
newOrbitMask.pieces.push({ facelets });
}
newMask.orbits[orbit] = newOrbitMask;
}
return newMask;
}
const kpuzzle = await cube3x3x3.kpuzzle();
twistyPlayer.experimentalStickeringMaskOrbits = transformMask(
json,
kpuzzle.algToTransformation(stickeringOrientation)
);
}
versus
async function setStickersString(mask: MaskT) {
const cubies = new CubieCube().apply(stickeringOrientation);
const regular = '-';
const muted = 'I';
let edges = 'EDGES:';
//[UF, UL, UB, UR, DF, DL, DB, DR, FL, BL, BR, FR];
//[UF, UR, UB, UL, DF, DR, DB, DL, FR, FL, BR, BL];
const edgePerm = [0, 3, 2, 1, 4, 7, 6, 5, 11, 8, 10, 9];
for (let j = 0; j < edgePerm.length; ++j) {
const i = cubies.ep[edgePerm[j]];
if (mask.ep[i] === 1) {
edges += regular;
} else {
edges += muted;
}
}
let corners = 'CORNERS:';
// 0 . 1 . 2 . 3 . 4 . 5 . 6 . 7
//[ULF, UBL, URB, UFR, DFL, DLB, DBR, DRF];
//[UFR, URB, UBL, ULF, DRF, DFL, DLB, DBR
const cornerPerm = [3, 2, 1, 0, 7, 4, 5, 6];
for (let j = 0; j < cornerPerm.length; ++j) {
const i = cubies.cp[cornerPerm[j]];
if (mask.cp[i] === 1) {
corners += regular;
} else {
corners += muted;
}
}
let centers = 'CENTERS:';
//[0, 1, 2, 3, 4, 5];
//[U, D, F, B, L, R];
//[U, L, F, R, B, D
const centerPerm = [0, 4, 2, 5, 3, 1];
for (let j = 0; j < centerPerm.length; ++j) {
const i = cubies.tp[centerPerm[j]];
if (!mask.tp || mask.tp[i] === 1) {
centers += regular;
} else {
centers += muted;
}
}
twistyPlayer.experimentalStickeringMaskOrbits = `${edges},${corners},${centers}`;
}
For my purposes, this issue is almost 100% done because the string generating code I have is working for my use cases.
There are two remaining issues, one that I don't care about and one that I do. The one I don't care about is that the JSON format doesn't work with PG3D, which doesn't matter to me since that's not the interface I am using though it is an out and out bug as far as I can tell.
There is a documentation bug in this thread where the meanings of -
and D
are accidentally reversed (comment https://github.com/cubing/cubing.js/issues/224#issuecomment-1264901286).
The problem I do care about is that @
doesn't have a distinct display to it. Now that I have stickering working for reconstructions, I want to use this display to enable the user to define their own masks for solution stages, and that UI will be difficult to use if @
is not distinctly rendered. I most likely will care about this for the unfolded 2D version of the puzzle as that is likely what I'll use for the UI in this case. I may be able to work around it by setting the orientation to an incorrectly oriented cubie as the visual difference between "solve it" and "position it".
Here they are for comparison:
The first one seems a bit overkill to me. Does this work? (I can't test it myself, so it might need some fixups. I'm assuming your leaf mask array values are 0
and 1
.)
const R = { facelets: new Array(3).fill("regular") };
const D = { facelets: new Array(3).fill("dim") };
const orderMap = [D, R];
const orbit = (m) => ({ pieces: m.map((v) => orderMap[v]) });
const json = {
orbits: {
EDGES: orbit(mask.ep),
CORNERS: orbit(mask.cp),
CENTERS: orbit(mask.tp),
},
};
The one I don't care about is that the JSON format doesn't work with PG3D
Yeah, this is related to https://github.com/cubing/cubing.js/issues/230 This will be fixed before next published release.
The problem I do care about is that
@
doesn't have a distinct display to it. Now that I have stickering working for reconstructions, I want to use this display to enable the user to define their own masks for solution stages, and that UI will be difficult to use if@
is not distinctly rendered. I most likely will care about this for the unfolded 2D version of the puzzle as that is likely what I'll use for the UI in this case. I may be able to work around it by setting the orientation to an incorrectly oriented cubie as the visual difference between "solve it" and "position it".
This one will take a while to get right (basically prepping for a full ACube.js-style UI), but I can think of some okay short-term workarounds, especially if you just need it for 2D.
Do you have any specific use cases that need this immediately? As far as I know, it's rare to have such pieces during a solution step — the only one off the top of my head is EJLS.
One way of looking at the two different implementations is how I chose to handle the ordering difference between CubieCube
and KState
; in the version that makes the strings the ordering permutations are arrays of numbers, and in the other approach they are arrays of array references. It is possible to convert the current code into something similar to what you've written by taking the same approach and building the permutations into the map
, but in the finished code it's not materially different whether the map
generates a string or generates an array of facelet objects. FWIW the 'completed' implementation looks like this:
async function setStickersString(mask: MaskT, priorMask?: MaskT) {
const cubies = new CubieCube().apply(stickeringOrientation);
const regular = '-';
const dim = 'D';
const muted = 'I';
function stringify(
perm: number[],
cubies: number[],
mask: number[],
prior: number[] | undefined
) {
const ret: string[] = [];
for (let j = 0; j < perm.length; ++j) {
const i = cubies[perm[j]];
if (mask[i] === 1) {
if (prior && prior[i] === 1) ret.push(dim);
else ret.push(regular);
} else {
ret.push(muted);
}
}
return ret.join('');
}
//[UF, UL, UB, UR, DF, DL, DB, DR, FL, BL, BR, FR];
//[UF, UR, UB, UL, DF, DR, DB, DL, FR, FL, BR, BL];
const edgePerm = [0, 3, 2, 1, 4, 7, 6, 5, 11, 8, 10, 9];
const edges =
'EDGES:' + stringify(edgePerm, cubies.ep, mask.ep, priorMask ? priorMask.ep : undefined);
// 0 . 1 . 2 . 3 . 4 . 5 . 6 . 7
//[ULF, UBL, URB, UFR, DFL, DLB, DBR, DRF];
//[UFR, URB, UBL, ULF, DRF, DFL, DLB, DBR
const cornerPerm: number[] = [3, 2, 1, 0, 7, 4, 5, 6];
const corners =
'CORNERS:' + stringify(cornerPerm, cubies.cp, mask.cp, priorMask ? priorMask.cp : undefined);
//[0, 1, 2, 3, 4, 5];
//[U, D, F, B, L, R];
//[U, L, F, R, B, D
const centerPerm = [0, 4, 2, 5, 3, 1];
const centers =
'CENTERS:' +
stringify(
centerPerm,
cubies.tp,
mask.tp || [1, 1, 1, 1, 1, 1],
priorMask && priorMask.tp ? priorMask.tp : undefined
);
twistyPlayer.experimentalStickeringMaskOrbits = `${edges},${corners},${centers}`;
}
and it's easy enough to imagine replacing muted
and regular
with R
and D
and making a JSON object that contains each of the subarrays instead of building the string. Seems to me like six of one and half a dozen of the other.
For the @
problem, I am going to try building the editor by twisting the 'placed but not oriented' cubies as the visual indicator. I think that'll be good enough - the user can click once on the mask to set it to ignore
, again to set it to position
and a third time to return to solved
. This seems quick and easy to implement and to use to create new masks. In general my plan for the solution editor is to let the users define any number of named masks, and separately any number of solution DAGs that chart paths through the masks (e.g. scrambled -> cross -> f2l -> oll_edges -> oll_corners -> pll_corners -> pll_edges to define beginner CFOP; but scrambled -> cross -> three_corners -> middle_edges -> all_edges -> position_5_corners -> orient_5_corners for my favourite beginner method). So having @
display differently is something I am likely to run up against as soon as I want to define the beginner method. Unfortunately as far as I can tell this particular method is lost in the mists of time -- I can find no online reference for it, though it is one of the oldest beginner methods due I believe to Jim McDonald as it is credited to him by Douglas Hofstadter in his March 1981 article for Scientific American.
It turned out I couldn't really see a way to use the stickering to build the mask editor. Instead, I used the raw SVG by copying the svg editor example and modifying it to my needs. So the @
problem does not yet prove to be relevant, since I can render whatever I like.
Alright, v0.31.0
is out: https://github.com/cubing/cubing.js/commit/668179c8bb116b24775e5450a5d949c38068d3a0
It turned out I couldn't really see a way to use the stickering to build the mask editor. Instead, I used the raw SVG by copying the svg editor example and modifying it to my needs. So the
@
problem does not yet prove to be relevant, since I can render whatever I like.
Sounds good! This definitely requires some experimentation, so I'll be interested to see what you come up with.
If you do find you'd like cubing.js
to have the direct functionality, it wouldn't be hard for us to support something like this in SVG, similar to Ryan Heise's ACube frontend as used in ACube.js
:
Steps to reproduce the issue
In my application I want to show the user alternate solutions for their stages. For example,
In the UI, when the user clicks on a stage, I was going to walk through that stage with an appropriate experimental stickering setting so they can focus on the relevant cubies in their solve as compared to the alternative solve.
Observed behaviour
Experimental stickering always assumes the white face is up, front face is green. It'll often be the case in these roux solves that the user will have the yellow face up and a different face forwards. I can't find a way to tell the experimental stickering what the orientation of the user's cube actually is.
Expected behaviour
Ideally I could define the stickering in any orientation I want. The only workaround I can see is to generate an image and use picture stickering, which will let me render the cube arbitrarily.
Environment
MacOS Chrome
🖼 Screenshots
No response
Additional info
No response