Skip to content

Commit

Permalink
solve NRG boards using Dijkstra's + cycling algs
Browse files Browse the repository at this point in the history
stdout:

   *  '0  '1  '2
  '3  '4  '5  '6
  '7   X   X   X
  '8   X   X   X
K=3,diameter=34,BFS time=38
ncombos=362880
0:1
22:8
24:22
26:14
28:26
30:44
32:42
34:12
44:34
46:222
48:327
50:482
52:786
54:1085
56:1017
58:940
60:729
62:594
64:370
66:244
68:870
70:2544
72:3704
74:6404
76:7326
78:7966
80:8666
82:7676
84:6778
86:5826
88:4318
90:3964
92:6666
94:9369
96:14164
98:16066
100:12729
102:11741
104:11267
106:8449
108:7138
110:4610
112:2962
114:1310
116:1080
118:508
120:276
122:24
124:32
126:8
# combos reached=181440
Warning: ncombos=362880!=reached=181440 (could be the result of parity restriction).
diameter=126
BFS time (ms)=14505
test array: [1, 2, 3, 4, 5, 6, 7, 8, 0]
URRUURURUURRUURRUURRUURDLDDLLD DRDRDLLDDLLDDLLDDLLDLULU ULDDRRDRUURRUURRUURRUURULLUURD RDDRRDRUURRUURRUURRUURULLUUL 
   *   0   1   2   3
  '4   5   6   7   8
  '9   X   X   X   X
 '10   X   X   X   X
 '11   X   X   X   X
K=3,diameter=44,BFS time=22
K=4,diameter=48,BFS time=285
ncombos=11880
0:1
30:1
32:36
34:21
35:24
36:38
37:17
38:64
39:24
40:72
41:24
42:92
43:27
44:98
45:8
46:30
48:1
62:4
64:292
65:24
66:460
67:500
68:704
69:398
70:944
71:754
72:818
73:622
74:718
75:602
76:914
77:464
78:854
79:434
80:696
81:262
82:474
83:122
84:180
85:34
86:26
87:2
# combos reached=11880
diameter=87
BFS time (ms)=14933
test array: [2, 1, 3, 0]
LUULDRURULURULURDLDDRDLURULURULDRDDLDDLURDDR LDRRUURULDRDLDLDRULURRULURDLDLDRDLULUR 
   *  '0  '1  '2  '3
   X  '4  '5  '6  '7
   X   X   X   X   X
   X   X   X   X   X
   X   X   X   X   X
K=3,diameter=44,BFS time=7
K=4,diameter=48,BFS time=278
ncombos=40320
0:1
32:4
34:10
35:8
36:32
37:8
38:72
39:14
40:65
41:29
42:32
43:6
44:34
45:2
46:5
48:1
66:20
67:44
68:140
69:162
70:534
71:375
72:947
73:680
74:1174
75:508
76:864
77:342
78:490
79:148
80:219
81:39
82:58
83:17
84:5
86:3
101:230
102:400
103:1348
104:1844
105:2350
106:2126
107:1456
108:1232
109:718
110:766
111:234
112:160
113:98
114:48
115:36
116:14
117:4
118:4
# combos reached=20160
Warning: ncombos=40320!=reached=20160 (could be the result of parity restriction).
diameter=118
BFS time (ms)=4589
test array: [2, 1, 3, 4, 5, 6, 7, 0]
RDRDRDLURULLURULDRDRDLDRULUULURDLL ULDLULDRDDLDRULURULURDLDDRDLURULURRURD LURRDLULDRURULDDRULLDRURDLULURDD 

Process finished with exit code 0
  • Loading branch information
coolcomputery committed Jul 12, 2021
1 parent 1f5097a commit b65edc9
Showing 1 changed file with 240 additions and 0 deletions.
240 changes: 240 additions & 0 deletions LoopoverNRGCycleDijkstra.java
@@ -0,0 +1,240 @@
import java.util.*;
/**
* consider a NxN NRG Loopover board with a binary matrix A
* where the piece at location (r,c) is solved iff A[r][c]==false
* and the gripped piece is solved and at (gr,gc)
* we say that we are currently at the "state" A
* furthermore, we want to go from state A to state B
* where B is another matrix satisfying (!A[r][c]-->!B[r][c]) for all r,c
* i.e. if A[r][c]==false then B[r][c] must also be false
* we will restrict ourselves to solving every scramble with 3-cycles and 2,2-cycles (more generally, blobs)
* additionally, we never make any already solved pieces become unsolved, except when in the process of performing a blob
* i.e. after each blob, all (r,c) s.t. A[r][c]==true are still in their solved positions
*/
public class LoopoverNRGCycleDijkstra {
private static int mod(int n, int k) {
return (n%k+k)%k;
}
private static String inv(String mvs) {
StringBuilder out=new StringBuilder();
for (int i=mvs.length()-1; i>-1; i--) {
char c=mvs.charAt(i);
out.append(c=='D'?'U':c=='R'?'L':c=='U'?'D':'R');
}
return out.toString();
}
public static String mvseqStr(List<int[]> S) {
StringBuilder str=new StringBuilder();
for (int[] m:S)
str.append(" ").append(m[0]==0?"R":"C").append(m[1]).append(m[2]==1?"":m[2]==-1?"'":("("+m[2]+")"));
return str.toString();
}
public static boolean[][] tobmat(String[] rows) {
boolean[][] out=new boolean[rows.length][rows[0].length()];
for (int i=0; i<out.length; i++)
for (int j=0; j<rows[i].length(); j++) {
char c=rows[i].charAt(j);
if (c=='0') out[i][j]=false;
else if (c=='1') out[i][j]=true;
else throw new RuntimeException("Not parseable as a binary matrix.");
}
return out;
}
private int R, C;
private int Nfree;
private int[] tofree, freeto;
public int K;
//BFS stuff
public int ncombos;
private int[] depth, par, blobi;
private List<String> blobs;
public int D, diam;
public LoopoverNRGCycleDijkstra(int gr, int gc, String A, String B) {
this(gr,gc,tobmat(A.split(",")),tobmat(B.split(",")));
}
private LoopoverNRGCycleDijkstra(int gr, int gc, boolean[][] A, boolean[][] B) {
long st=System.currentTimeMillis();
R=A.length; C=A[0].length;
if (R!=C) throw new RuntimeException("Only square board sizes allowed."); //need to refactor LoopoverNRGSetup before I can remove this constraint
if (R!=B.length||C!=B[0].length) throw new RuntimeException("Mismatch in dimensions.");
if (A[gr][gc]) throw new RuntimeException("Gripped piece not marked as solved.");
for (int r=0; r<R; r++) for (int c=0; c<C; c++)
if (!A[r][c]&&B[r][c]) throw new RuntimeException("Set of solved pieces in A does not subset set of solved pieces in B.");
Nfree=0; tofree=new int[R*C]; freeto=new int[R*C];
for (int r=0; r<R; r++) for (int c=0; c<C; c++) if (A[r][c]) {
tofree[r*C+c]=Nfree;
freeto[Nfree]=r*C+c;
Nfree++;
}
freeto=Arrays.copyOfRange(freeto,0,Nfree);
for (int r=0; r<R; r++) {
for (int c=0; c<C; c++)
System.out.printf("%4s",
r==gr&&c==gc?"*":
(A[r][c]?
((B[r][c]?"":"'"))+tofree[r*C+c]
:"X")
//X: locked; ': piece that this BFS tree tries to solve; *: gripped piece
);
System.out.println();
}
blobs=new ArrayList<>(); List<int[]> blobactions=new ArrayList<>();
LoopoverNRGSetup[] bfss={LoopoverNRGSetup.cyc3bfs(R), LoopoverNRGSetup.swap22bfs(R)};
for (LoopoverNRGSetup bfs:bfss) if (bfs!=null) { //table of all possible 3-cycle algorithms we will need
int offset=blobs.size();
int T=bfs.tP.length; //the number of pieces that will be cycled
for (int rep=0; rep<Math.pow(Nfree,T); rep++) {
blobs.add(null);
blobactions.add(null);
}
int[] P=bfs.tP;
int[] tuple=new int[T];
while (tuple[T-1]<Nfree) {
boolean good=true;
for (int i=0; i<T&&good; i++)
for (int j=0; j<i&&good; j++)
if (tuple[i]==tuple[j]) good=false;
if (good) {
int i=tupleCode(tuple);
{
int[] tl=new int[T]; for (int ii=0; ii<T; ii++) tl[ii]=freeto[tuple[ii]];
blobs.set(offset+i,bfs.sol(tl,gr,gc));
}
int[] action=new int[Nfree];
for (int k=0; k<Nfree; k++) action[k]=k;
for (int p=0; p<T; p++)
action[tuple[p]]=tuple[P[p]];
//applying the move sequence bfs.sol(tl,gr,gc) moves the k-th free location to the action[k]-th free location
blobactions.set(offset+i,action);
}
tuple[0]++;
for (int ti=0; ti<T-1&&tuple[ti]==Nfree; ti++) {
tuple[ti]=0;
tuple[ti+1]++;
}
}
}
//BFS
K=0; int[] solvedscrm=new int[R*C];
for (int r=0; r<R; r++) for (int c=0; c<C; c++) if (A[r][c]&&!B[r][c]) solvedscrm[K++]=tofree[r*C+c];
long tmp=1;
for (int rep=0; rep<K; rep++)
tmp*=Nfree-rep;
if (tmp>400_000_000) throw new RuntimeException("Too many combos: "+tmp);
ncombos=(int)tmp;
depth=new int[ncombos]; Arrays.fill(depth,Integer.MAX_VALUE); par=new int[ncombos]; blobi=new int[ncombos];
System.out.println("ncombos="+ncombos);
List<Set<Integer>> fronts=new ArrayList<>(); fronts.add(new HashSet<>());
int solvedcode=comboCode(Arrays.copyOfRange(solvedscrm,0,K));
fronts.get(0).add(solvedcode);
depth[solvedcode]=0; par[solvedcode]=-1; blobi[solvedcode]=-1;
int reached=0;
for (D=0, diam=0; D<fronts.size(); D++)
if (fronts.get(D)!=null&&fronts.get(D).size()>0) {
diam=Math.max(diam,D);
System.out.println(D+":"+fronts.get(D).size());
reached+=fronts.get(D).size();
for (int f:fronts.get(D)) {
int[] scrm=codeCombo(f);
for (int ci = 0; ci< blobs.size(); ci++) if (blobactions.get(ci)!=null) {
int nf=comboCode(scrm,blobactions.get(ci));
int ndepth=depth[f]+ blobs.get(ci).length();
if (ndepth<depth[nf]) {
if (depth[nf]!=Integer.MAX_VALUE)
fronts.get(depth[nf]).remove(nf);
depth[nf]=ndepth;
par[nf]=f;
blobi[nf]=ci;
while (ndepth>=fronts.size()) fronts.add(null);
if (fronts.get(ndepth)==null) fronts.set(ndepth,new HashSet<>());
fronts.get(ndepth).add(nf);
}
}
}
}
System.out.println("# combos reached="+reached);
if (reached!=ncombos) System.out.println("Warning: ncombos="+ncombos+"!=reached="+reached+" (could be the result of parity restriction).");
System.out.println("diameter="+diam);
System.out.println("BFS time (ms)="+(System.currentTimeMillis()-st));
}
private int tupleCode(int[] vs) {
int out=0;
for (int i=0, pow=1; i<vs.length; i++, pow*=Nfree)
out+=vs[i]*pow;
return out;
}
private int comboCode(int[] A) {
int[] P=new int[Nfree];
for (int i=0; i<Nfree; i++) P[i]=i;
int[] L=P.clone();
int out=0;
for (int i=Nfree-1, pow=1; i>=Nfree-K; i--) {
//swap idxs i and L[A[i-(N-K)]] in P
int j=L[A[i-(Nfree-K)]];
int pi=P[i];//, pj=P[j];
//P[i]=pj; //<--idx i will never be touched again
P[j]=pi;
L[pi]=j;
//L[pj]=i;
//J_i==j
out+=pow*j;
pow*=i+1;
}
return out;
}
private int comboCode(int[] A, int[] f) {
int[] P=new int[Nfree];
for (int i=0; i<Nfree; i++) P[i]=i;
int[] L=P.clone();
int out=0;
for (int i=Nfree-1, pow=1; i>=Nfree-K; i--) {
int j=L[f[A[i-(Nfree-K)]]];
int pi=P[i];
P[j]=pi;
L[pi]=j;
out+=pow*j;
pow*=i+1;
}
return out;
}
private int[] codeCombo(int code) {
int[] P=new int[Nfree];
for (int i=0; i<Nfree; i++) P[i]=i;
for (int v=Nfree; v>Nfree-K; v--) {
int i=v-1, j=code%v;
code/=v;
int pi=P[i]; P[i]=P[j]; P[j]=pi;
}
int[] out=new int[K];
System.arraycopy(P,Nfree-K,out,0,K);
return out;
}
public String solveseq(int code) {
if (depth[code]==Integer.MAX_VALUE)
throw new RuntimeException("No solution.");
StringBuilder out=new StringBuilder();
for (int c=code; depth[c]>0; c=par[c])
out.append(inv(blobs.get(blobi[c]))).append(" ");
return out.toString();
}
public String solveseq(int[] scramble) {
return solveseq(comboCode(scramble));
}
private String test() {
int[] tscrm=new int[K];
for (int i=0; i<K; i++) tscrm[i]=(i+1)%K;
if (K%2==0) {
int tmp=tscrm[0]; tscrm[0]=tscrm[1]; tscrm[1]=tmp;
}
System.out.println("test array: "+Arrays.toString(tscrm));
return solveseq(tscrm);
}
public static void main(String[] args) {
//4x4 NRG, JKL NOP already solved (takes at most 24 moves)
System.out.println(new LoopoverNRGCycleDijkstra(0,0,"0111,1111,1000,1000","0000,0000,0000,0000").test()); //solve BCD EFGH I M

//5x5 NRG, LMNO QRST VWXY already solved (takes at most 21+24+66=111 moves)
System.out.println(new LoopoverNRGCycleDijkstra(0,0,"01111,11111,10000,10000,10000","01111,01111,00000,00000,00000").test()); //solve F K P U
System.out.println(new LoopoverNRGCycleDijkstra(0,0,"01111,01111,00000,00000,00000","00000,00000,00000,00000,00000").test()); //solve BCDE GHIJ
}
}

0 comments on commit b65edc9

Please sign in to comment.