commit 841f82f02bac8c6dfede51067c2bdea819980ef7
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Mar 20 00:38:56 2023 +0100

    Jing tablebase. New, less pillowed Jing mesh.

diff --git a/src/main/java/org/distorted/solvers/SolverJing.java b/src/main/java/org/distorted/solvers/SolverJing.java
new file mode 100644
index 00000000..b5c95368
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverJing.java
@@ -0,0 +1,535 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.solvers;
+
+import android.content.res.Resources;
+
+import org.distorted.main.R;
+import org.distorted.objectlib.main.ObjectSignatures;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TBPyraminx;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverJing extends SolverTablebase
+{
+  private static final int ERROR_CORNER_GYB_MISSING = -1;
+  private static final int ERROR_CORNER_GYR_MISSING = -2;
+  private static final int ERROR_CORNER_GBR_MISSING = -3;
+  private static final int ERROR_CORNER_YBR_MISSING = -4;
+
+  private static final int ERROR_EDGE_RB_MISSING    = -5;
+  private static final int ERROR_EDGE_RY_MISSING    = -6;
+  private static final int ERROR_EDGE_RG_MISSING    = -7;
+  private static final int ERROR_EDGE_YB_MISSING    = -8;
+  private static final int ERROR_EDGE_GB_MISSING    = -9;
+  private static final int ERROR_EDGE_GY_MISSING    = -10;
+
+  private static final int ERROR_CENTER_G_MISSING   = -11;
+  private static final int ERROR_CENTER_Y_MISSING   = -12;
+  private static final int ERROR_CENTER_B_MISSING   = -13;
+  private static final int ERROR_CENTER_R_MISSING   = -14;
+
+  private static final int ERROR_CORNERS_CANNOT     = -15;
+  private static final int ERROR_EDGE_TWISTED       = -16;
+  private static final int ERROR_CORNER_TWISTED     = -17;
+  private static final int ERROR_TWO_EDGES          = -18;
+  private static final int ERROR_TWO_CENTERS        = -19;
+
+  private TablebasesAbstract mSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverJing(Resources res, TwistyObject object)
+    {
+    super(res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean pieceEqual3(int[] piece, int c1, int c2, int c3)
+    {
+    return ( (piece[0]==c1 && piece[1]==c2 && piece[2]==c3) ||
+             (piece[0]==c1 && piece[2]==c2 && piece[1]==c3) ||
+             (piece[1]==c1 && piece[0]==c2 && piece[2]==c3) ||
+             (piece[1]==c1 && piece[2]==c2 && piece[0]==c3) ||
+             (piece[2]==c1 && piece[1]==c2 && piece[0]==c3) ||
+             (piece[2]==c1 && piece[0]==c2 && piece[1]==c3)  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean pieceEqual2(int[] piece, int[] colors)
+    {
+    return ( (piece[0]==colors[0] && piece[1]==colors[1]) ||
+             (piece[0]==colors[1] && piece[1]==colors[0])  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCornersPresent(int[][] corners)
+    {
+    boolean ybr = false;
+    boolean gbr = false;
+    boolean gyr = false;
+    boolean gyb = false;
+
+    for(int i=0; i<4; i++)
+      {
+      if( pieceEqual3(corners[i],0,1,2) ) gyb = true;
+      if( pieceEqual3(corners[i],0,1,3) ) gyr = true;
+      if( pieceEqual3(corners[i],0,2,3) ) gbr = true;
+      if( pieceEqual3(corners[i],1,2,3) ) ybr = true;
+      }
+
+    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
+    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
+    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
+    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllEdgesPresent(int[][] edges, int[][] edgeColors)
+    {
+    boolean[] present = new boolean[6];
+    for(int i=0; i<6; i++) present[i] = false;
+
+    for(int i=0; i<6; i++)
+      for(int j=0; j<6; j++)
+        if (pieceEqual2(edges[i], edgeColors[j]))
+          {
+          present[j] = true;
+          break;
+          }
+
+    if( !present[0] ) return ERROR_EDGE_RB_MISSING;
+    if( !present[1] ) return ERROR_EDGE_GB_MISSING;
+    if( !present[2] ) return ERROR_EDGE_RG_MISSING;
+    if( !present[3] ) return ERROR_EDGE_YB_MISSING;
+    if( !present[4] ) return ERROR_EDGE_RY_MISSING;
+    if( !present[5] ) return ERROR_EDGE_GY_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCentersPresent(int[] centers)
+    {
+    boolean[] present = new boolean[4];
+    for(int i=0; i<4; i++) present[i] = false;
+
+    for(int i=0; i<4; i++)
+      {
+      present[centers[i]]= true;
+      }
+
+    if( !present[0] ) return ERROR_CENTER_G_MISSING;
+    if( !present[1] ) return ERROR_CENTER_Y_MISSING;
+    if( !present[2] ) return ERROR_CENTER_B_MISSING;
+    if( !present[3] ) return ERROR_CENTER_R_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeFaceColors(int[][] corners)
+    {
+    int[] ret = new int[4];
+
+    for(int i=0; i<4; i++)
+      for(int j=0; j<4; j++)
+        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computePieceTwist(int index, int[] corner, int[] faceColor)
+    {
+    int twist1=0, twist2=0, twist3=0;
+
+    switch(index)
+      {
+      case 0: if( corner[1]==faceColor[1] ) twist1=1;
+              if( corner[2]==faceColor[1] ) twist1=2;
+              if( corner[0]==faceColor[2] ) twist2=2;
+              if( corner[2]==faceColor[2] ) twist2=1;
+              if( corner[0]==faceColor[3] ) twist3=1;
+              if( corner[1]==faceColor[3] ) twist3=2;
+              break;
+      case 1: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[2] ) twist2=1;
+              if( corner[1]==faceColor[2] ) twist2=2;
+              if( corner[0]==faceColor[3] ) twist3=2;
+              if( corner[2]==faceColor[3] ) twist3=1;
+              break;
+      case 2: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[1] ) twist2=2;
+              if( corner[2]==faceColor[1] ) twist2=1;
+              if( corner[0]==faceColor[3] ) twist3=1;
+              if( corner[1]==faceColor[3] ) twist3=2;
+              break;
+      case 3: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[1] ) twist2=1;
+              if( corner[1]==faceColor[1] ) twist2=2;
+              if( corner[0]==faceColor[2] ) twist3=2;
+              if( corner[2]==faceColor[2] ) twist3=1;
+              break;
+      }
+
+    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int locateEdge(int[][] edges, int[] colors)
+    {
+    for(int i=0; i<6; i++)
+      if( edges[i][0]==colors[0] && edges[i][1]==colors[1] ||
+          edges[i][0]==colors[1] && edges[i][1]==colors[0]  ) return i;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int edgeTwist(int[] edge, int[] colors)
+    {
+    return edge[0]==colors[0] ? 0:1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeEdgeQuats(int[][] edges, int[][] edgeColors)
+    {
+    int[] quats = new int[6];
+
+    for(int i=0; i<6; i++)
+      {
+      int pos   = locateEdge(edges,edgeColors[i]);
+      int twist = edgeTwist(edges[pos],edgeColors[i]);
+      quats[i]  = TBPyraminx.EDGE_QUATS[pos][twist];
+      }
+
+    return quats;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// GRB YBR YGB YRG
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    for(int i=0; i<4; i++)
+      for(int j=0; j<3; j++)
+        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// RB GB RG YB RY GY
+
+  private void getEdges(TwistyObject object, int[][] edges)
+    {
+    for(int i=0; i<6; i++)
+      for(int j=0; j<2; j++)
+        edges[i][j] = object.getCubitFaceStickerIndex(i+4,j) -4;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// G Y B R
+
+  private void getCenters(TwistyObject object, int[] centers)
+    {
+    for(int i=0; i<4; i++)
+      centers[i] = object.getCubitFaceStickerIndex(i+10,0) - 8;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] computeEdgeColors(int[] faceColor)
+    {
+    // The first pair being (2,3) means 'the first edge's first face is on the tetrahedron
+    // face which opposes corner number 2, and its second face on tetra face which opposes
+    // corner number 3'
+    // Order of those pairs determines edge twist.
+
+    final int[][] edgeColorIndices = new int[][] { {2,3},{1,3},{2,1},{0,3},{2,0},{1,0}  };
+    int[][] ret = new int[6][2];
+
+    for(int i=0; i<6; i++)
+      {
+      ret[i][0] = faceColor[edgeColorIndices[i][0]];
+      ret[i][1] = faceColor[edgeColorIndices[i][1]];
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCenterTwist(int[] centers, int color1, int color2)
+    {
+    if( centers[0]==color1 )
+      {
+      if( centers[1]==color2 ) return 1;
+      if( centers[2]==color2 ) return 2;
+      if( centers[3]==color2 ) return 0;
+
+      return -1;
+      }
+    if( centers[1]==color1 )
+      {
+      if( centers[0]==color2 ) return 1;
+      if( centers[2]==color2 ) return 0;
+      if( centers[3]==color2 ) return 2;
+
+      return -1;
+      }
+    if( centers[2]==color1 )
+      {
+      if( centers[0]==color2 ) return 2;
+      if( centers[1]==color2 ) return 0;
+      if( centers[3]==color2 ) return 1;
+
+      return -1;
+      }
+    if( centers[3]==color1 )
+      {
+      if( centers[0]==color2 ) return 0;
+      if( centers[1]==color2 ) return 2;
+      if( centers[2]==color2 ) return 1;
+
+      return -1;
+      }
+
+    return -2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceOfCenter(int color, int[] centers)
+    {
+    if( centers[0]==color ) return 0;
+    if( centers[1]==color ) return 1;
+    if( centers[2]==color ) return 2;
+    if( centers[3]==color ) return 3;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners   = new int[4][3];
+    int[][] edges     = new int[6][2];
+    int[] centers     = new int[4];
+    int[] corner_twist= new int[4];
+
+    getCorners(object,corners);
+
+    int result1 = checkAllCornersPresent(corners);
+    if( result1<0 ) return result1;
+
+    int[] faces = computeFaceColors(corners);
+    int[][] edgeColors = computeEdgeColors(faces);
+
+    getEdges(object,edges);
+    int result2 = checkAllEdgesPresent(edges,edgeColors);
+    if( result2<0 ) return result2;
+
+    getCenters(object,centers);
+    int result3 = checkAllCentersPresent(centers);
+    if( result3<0 ) return result3;
+
+    for(int i=0; i<4; i++)
+      {
+      corner_twist[i] = computePieceTwist(i,corners[i],faces);
+      if( corner_twist[i]<0 ) return ERROR_CORNERS_CANNOT;
+      }
+
+    int[] quats = computeEdgeQuats(edges,edgeColors);
+    int[] permutation = new int[6];
+    TBPyraminx.getEdgePermutation(permutation,quats,0);
+    boolean even = TablebaseHelpers.permutationIsEven(permutation);
+    if( !even ) return ERROR_TWO_EDGES;
+    int[] edge_twist = new int[6];
+    TBPyraminx.getEdgeTwist(edge_twist,quats,0);
+/*
+for(int i=0; i<4; i++) android.util.Log.e("D", "cornerTwist "+i+" : "+corner_twist[i]);
+for(int i=0; i<6; i++) android.util.Log.e("D", "edgeTwist "+i+" : "+edge_twist[i]);
+for(int i=0; i<6; i++) android.util.Log.e("D", "edge perm "+i+" : "+permutation[i]);
+*/
+    int twist_gr = computeCenterTwist(centers,faces[1],faces[2]);
+    int twist_ry = computeCenterTwist(centers,faces[2],faces[0]);
+
+    if( (twist_ry-twist_gr+1)%3 != 0 ) return ERROR_TWO_CENTERS;
+
+    int total_twist=0;
+    for(int i=0; i<4; i++) total_twist += corner_twist[i];
+
+    if( (total_twist-twist_gr)%3 !=0 ) return ERROR_CORNER_TWISTED;
+
+    int green_face = getFaceOfCenter(faces[1],centers);
+
+    int totalEdgeTwist=0;
+    for(int i=0; i<6; i++) totalEdgeTwist += edge_twist[i];
+    if( (totalEdgeTwist%2)!=0 ) return ERROR_EDGE_TWISTED;
+
+    int vertexTwist = corner_twist[0]+ 3*(corner_twist[1]+ 3*(corner_twist[2]+ 3*corner_twist[3]));
+    int edgeTwist = edge_twist[0]+ 2*(edge_twist[1]+ 2*(edge_twist[2]+ 2*(edge_twist[3]+ 2*edge_twist[4])));
+    int perm_num = TablebaseHelpers.computeEvenPermutationNum(permutation);
+
+//android.util.Log.e("D", "ret: green_face="+green_face+" vertTwist="+vertexTwist+" edgeTwist="+edgeTwist+" perm: "+perm_num);
+
+    return green_face + 4*(vertexTwist + 81*(edgeTwist + 32*perm_num));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green3;
+      case 1: return R.string.color_yellow3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green4;
+      case 1: return R.string.color_yellow4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_red4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex6(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green6;
+      case 1: return R.string.color_yellow6;
+      case 2: return R.string.color_blue6;
+      case 3: return R.string.color_red6;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int color0, int color1, int color2)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex3(color1);
+    int j2 = getColorIndex4(color2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeError(Resources res, int color0, int color1)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex6(color1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_YBR_MISSING: return cornerError(res,3,2,1);
+      case ERROR_CORNER_GBR_MISSING: return cornerError(res,3,2,0);
+      case ERROR_CORNER_GYR_MISSING: return cornerError(res,3,1,0);
+      case ERROR_CORNER_GYB_MISSING: return cornerError(res,2,1,0);
+      case ERROR_EDGE_RB_MISSING   : return edgeError(res,3,2);
+      case ERROR_EDGE_RY_MISSING   : return edgeError(res,3,1);
+      case ERROR_EDGE_RG_MISSING   : return edgeError(res,3,0);
+      case ERROR_EDGE_YB_MISSING   : return edgeError(res,2,1);
+      case ERROR_EDGE_GB_MISSING   : return edgeError(res,2,0);
+      case ERROR_EDGE_GY_MISSING   : return edgeError(res,1,0);
+      case ERROR_CENTER_G_MISSING  : String colorG = res.getString(R.string.color_green2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorG);
+      case ERROR_CENTER_Y_MISSING  : String colorY = res.getString(R.string.color_yellow2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorY);
+      case ERROR_CENTER_B_MISSING  : String colorB = res.getString(R.string.color_blue2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorB);
+      case ERROR_CENTER_R_MISSING  : String colorR = res.getString(R.string.color_red2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorR);
+      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
+      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
+      case ERROR_TWO_CENTERS       : return res.getString(R.string.solver_generic_two_centers);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, Resources res)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createUnpacked(ObjectSignatures.JING_2);
+      if( mSolver!=null ) mSolver.createTablebase(-1);
+      }
+/*
+int[] q= {0,0,0,0, 0,0,0,0,0,0, 0,0,0,0};
+TBJing jing = new TBJing();
+int in= jing.getIndex(q);
+android.util.Log.e("D", "index = "+in);
+
+int[] s = jing.getQuats(0);
+
+for(int i=0; i<14; i++) android.util.Log.e("D", "quat = "+i+" : "+s[i]);
+*/
+    return mSolver!=null ? mSolver.solution(index,null) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverMain.java b/src/main/java/org/distorted/solvers/SolverMain.java
index b369c316..17a6aead 100644
--- a/src/main/java/org/distorted/solvers/SolverMain.java
+++ b/src/main/java/org/distorted/solvers/SolverMain.java
@@ -106,6 +106,11 @@ public class SolverMain implements Runnable
       SolverTablebase solver = new SolverCube2(mRes,mObject);
       solver.solve(screen);
       }
+    else if( mSignature==ObjectSignatures.JING_2 )
+      {
+      SolverTablebase solver = new SolverJing(mRes,mObject);
+      solver.solve(screen);
+      }
     else
       {
       screen.displayErrorDialog(mRes.getString(R.string.solver_generic_not_implemented));
diff --git a/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java b/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java
index 1276ef1d..ac0c1f14 100644
--- a/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java
+++ b/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java
@@ -101,7 +101,7 @@ public class SolverPyraminxDuo extends SolverTablebase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private int[] computeMissingCornerColor(int[][] corners)
+  private int[] computeFaceColor(int[][] corners)
     {
     int[] ret = new int[4];
 
@@ -222,10 +222,10 @@ public class SolverPyraminxDuo extends SolverTablebase
     int result2 = checkAllCentersPresent(centers);
     if( result2<0 ) return result2;
 
-    int[] missing = computeMissingCornerColor(corners);
+    int[] faces = computeFaceColor(corners);
 
-    int twist_gr = computeCenterTwist(centers,missing[1],missing[2]);
-    int twist_ry = computeCenterTwist(centers,missing[2],missing[0]);
+    int twist_gr = computeCenterTwist(centers,faces[1],faces[2]);
+    int twist_ry = computeCenterTwist(centers,faces[2],faces[0]);
 
     if( (twist_ry-twist_gr+1)%3 != 0 ) return ERROR_TWO_CENTERS_SWAP;
 
@@ -233,14 +233,14 @@ public class SolverPyraminxDuo extends SolverTablebase
 
     for(int i=0; i<4; i++)
       {
-      corner_twist[i] = computeCornerTwist(i,corners[i],missing);
+      corner_twist[i] = computeCornerTwist(i,corners[i],faces);
       if( corner_twist[i]<0 ) return corner_twist[i];
       total_twist += corner_twist[i];
       }
 
     if( (total_twist-twist_gr)%3 !=0 ) return ERROR_CORNER_TWISTED;
 
-    int green_face = getFaceOfCenter(missing[1],centers);
+    int green_face = getFaceOfCenter(faces[1],centers);
 
     return green_face + 4*(corner_twist[3] + 3*(corner_twist[2] + 3*(corner_twist[1] + 3*corner_twist[0])));
     }
