php/rubik.php

<?php
/*
6 faces, 54 coloured squares
12 edges, 27 small cubes "cubies"

the 6 faces of the cube are numbered        the 9 squares/cublets in each face are
                   from 0 to 5              numberd counter clock wise

          3 white                           6 5 4
4 green   0 red     2 blue    5 orange      7 8 3
          1 yellow                          0 1 2 

            33 32 31
            34 35 30
            27 28 29

  42 41 40  06 05 04  24 23 22  51 50 49
  43 44 39  07 08 03  25 26 21  52 53 48
  36 37 38  00 01 02  18 19 20  45 46 47

            15 14 13
            16 17 12
            09 10 11

the address of a square is side * 9 + squareNo from 0 to 53

                012     234     456     670
red    0  0   y 654   b 076   w 210   g 432
yellow 1  9   o 210   b 210   r 210   g 210
blue   2 18   y 432   o 076   w 432   r 432
white  3 27   r 654   b 654   o 654   g 654
green  4 36   y 076   r 076   w 076   o 432
orange 5 45   y 210   g 076   w 654   b 432


times
tCells/5   solved 164 rots, 1.06e+8 = 105685137 tries, 195 secs at 2025-02-23T13:30:12
tCells/7   solved 172 rots, 1.45e+8 = 145101655 tries, 228 secs at 2025-02-23T13:38:07
tCells/6   solved 84 rots, 5.63e+7 = 56347636 tries, 102 secs, 65.937 back,  35.75 search, at 2025-02-23T14:39:38
tCells/6.5 solved 84 rots, 4.72e+7 = 47174445 tries, 97 secs, 55.549 back,  40.512 search, at 2025-02-23T14:46:58
tCells/6.8 solved 84 rots, 3.86e+7 = 38552163 tries, 82 secs, 39.822 back, 41.499 search, at 2025-02-23T14:58:24
tCells/7   solved 84 rots, 3.13e+7 = 31269090 tries, 63 secs, 21.544 back,  41.938 search, at 2025-02-23T14:43:12
tCells/7.2 solved 84 rots, 3.13e+7 = 31269090 tries, 63 secs, 21.58 back,  41.379 search, at 2025-02-23T14:49:44
tCells/7.2 solved 84 rots, 3.13e+7 = 31269090 tries, 64 secs, 21.461 back, 41.356 search, at 2025-02-23T14:53:49
tCells/7.4 solved 84 rots, 3.06e+7 = 30619055 tries, 64 secs, 20.794 back, 42.952 search, at 2025-02-23T14:55:50
*/

require_once 'env.php';

class Rubik {
    const COL = ['red', 'yellow', 'blue', 'white', 'green', 'orange']
        , COW = ['black', 'black', 'white', 'black', 'white', 'black']
        , ROT = ['?', '+', '++', '-', '?']
        , NR = [1 => [0=>3, 1=>0, 3=>5, 5=>1]
               ,2 => [0=>4, 2=>0, 4=>5, 5=>2]
                ]
        , ROT8 = ['1' => '3', '3' => '5', '5' => '7', '7' => '1', 'f' => 'f']
        , MEMMAX = 3000000000
        , SRCH = 'searcG'  # the search function/method
                           # compression decompression
        , COMP = 'hex2bin' # 'Rubik::compNo' #  'Rubik::comp19'
        , DECO = 'bin2hex' #'Rubik::compNo' #'Rubik::deco19';
        ;

    public static function compNo($c) { # compresses the back table keys to 27Bytes and makes it faster
        return $c;
    }
    public static function compHex($c) { # compresses the back table keys to 27Bytes and makes it faster
        return hex2bin($c);
    }

    public static function decoHex($c) { 
        return bin2hex($c);
    }

    public static function comp19($c) { # compresses the back table keys to 19Bytes and makes it slower - the cost of compression is too high
                                        # we have 6 colors and the wildcard which makes for each face 7**8 * 5 possibilites which needs 25bits
                                        #   compress that into 3 bytes plus 1 overflow bit
        $k = '';
        $o = 0;
        for ($f=0; $f<6; $f++) {
            $f7 = strtr($c[$f*9+8], '1357f', '01234') . strtr(substr($c, $f*9, 8), 'f', '6');
            $i = intval($f7, 7); 
            $o |=  ($i & (1<<24)) >> (19+$f);
            # dbq("COMP i $i", dechex($i) , ", o $o");
            $k .= chr(($i >>16) & 255) . chr(($i >>8) & 255) . chr($i & 255);
        } 

        # out("comp19 $c =>", bin2hex($r=$k . chr($o)), "=>", $q = self::deco19($r), $c === $q);
        #if($c !== self::deco19($k . chr($o)))
        #    err("comp19 $c =>", bin2hex($r=$k . chr($o)), "=>", $q = self::deco19($r), $c === $q);
        return $k . chr($o);
    }

    public static function deco19($k) {
        $o = ord($k[18]);
        # dbq("o $o, oD $oD");
        $c = '';
        for ($f=0; $f<6; $f++) {
            $i = (($o << (19+$f)) & (1 << 24)) | (ord($k[$f3=$f*3])<<16) | (ord($k[$f3+1])<<8) | ord($k[$f3+2]);
            $j = str_pad(base_convert((string) $i, 10, 7), 9, '0', STR_PAD_LEFT);
            # dbq("i $i =>j $j");
            $c .= strtr(substr($j, 1, 8), '6', 'f') . strtr($j[0], '01234', '1357f');
        } 
        return $c;
    }


    static $fRot, $rRot, $r2n, $rTrg, $r2tr, $tgt, $vis, $seq, $rC
        , $depthMax = 55
        , $backMax = 5;

 
    public static function linIO($t, $c) {
        if (OutHtml)
            self::linI($t, $c);
        elseif ($c !== '')
            self::linO($t, $c);
    }

    public static function config () {
        $c2r = []; # cartesian coordinate (x,y,z) <=> 100z + 10y +x, each 0,1,2 (yielding the 3**3 small cubes)
                  # to Rubrik Coordinates = face= * 9 + colourCell=smallSquareNo => 54 small squares + 10*y + >of the small cube (the center square changes orientation, not color)
        foreach ([[0, 1, 10], [200, 1, -100], [2, 100, 10], [20,1,100], [200, -100,10 ], [202, -1, 10]] as $f => $t) {
            $c2r[$a = $t[0]][] = $f*9;
            foreach ([1, 1, 2, 2, 5, 5, 6, 1] as $c => $v) 
                $c2r[($v & 4) ? $a -= $t[$v & 3] : $a += $t[$v & 3]][] = $f * 9 + $c + 1;
        }
        #dbq('config c2r', count($c2r), $c2r);
     
        $r2n = []; # Rubik Coordinates (9*f+c) => neighbours over the edges
        foreach ($c2r as $n) {
            if (count($n) !== 2)
                continue;
            $r2n[$n[0]] = $n;           # c in middle of the edge => [c, neighbour over the edge]
            $r2n[$n[1]] = [$n[1], $n[0]];
        }
        foreach ($c2r as $n) {
            if (count($n) !== 3)
                continue;
            foreach ($n as $x => $c) { # c a corner => [c, neighbour on face of neighbour of predessor, neighbour on face of neighbour of successor]
                $b = $r2n[($c % 9 === 0) ? $c+7 : $c-1][1]; # neighbour of predecessor
                $r2n[$c] = intdiv($n1 = $n[($x+1) % 3], 9) === intdiv($b, 9) ? [$c, $n1, $n[($x+2) % 3]] : [$c, $n[($x+2) % 3], $n1];
            }
        }
        ksort($r2n);
        self::$r2n = $old = $r2n;
          
        /*--- r2n gives the neighours of each Rubrik ColourCell 
            edge   e => [e, f] with f the cell over the edge e.g. 1 => [1, 14]
            vertex v => [v, w, x] with w the neigbour on the face over the edge before v and x the neigbour on the face over the edge after v, e.g. 2 => [2, 13, 18] 
        --- */    
        $r2n = [1 => [1, 14], 3 => [3, 25], 5 => [5, 28], 7 => [7, 39], 21 => [21, 52]]; # glue the the six faces at 5 edges
        do {
            $lC = count($r2n);
            # dbq("----while $lC", $r2n);
            for ($f=0; $f<54; $f+=9) {
                for ($c=1; $c<8; $c+=2) {
                    if (! ($g = $r2n[$f + $c] ?? false)) # current edge g not in r2n yet
                        continue;
                    $gR = array_reverse($g);
                    if ($gR !== $r2n[$g[1]] ??= $gR)
                        err("bad edge", $r2n[$g[1]], 'reverse', $gR);
                    if (! ($h = ($r2n[$f + ($c+2) % 8] ?? false))) # next edge h not yet defined
                        continue;
                        # v is the vertex between g and h, the orientation of neighbouring faces run in opposite direction along the connecting edge
                    $v = [$f + ($c+1) % 8, $g[1] - 1, $h[1] + (($h[1] % 9 < 7) ? +1 : -7)]; 
                    if ($v !== ($r2n[$v[0]] ??= $v))
                        err("bad vertex", $r2n[$v[0]], ' differs ', $v, $r2n);
                    ([$v[1], $v[2], $v[0]] === ($r2n[$v[1]] ??= [$v[1], $v[2], $v[0]])) or err("mismatch vertex 1", $v);
                    ([$v[2], $v[0], $v[1]] === ($r2n[$v[2]] ??= [$v[2], $v[0], $v[1]])) or err("mismatch vertex 2", $v);
                    $i = [$g[1] + (($g[1] % 9 >= 2) ? -2 : +6 ), $h[1] + (($h[1] % 9 < 6) ? +2 : -6 )];  # i is neighbourhood of third edge of vertex   
                    if ($i !== ($r2n[$i[0]] ??= $i))
                        err("third edge", $i, 'mismatches', $r2n[$i[0]]);
                }
            }
        } while ($lC !== count($r2n));
        ksort($r2n);
        self::$r2n = $old = $r2n;
        $r2n === $old or err("config r2n", $r2n === $old, "\nnew", count($r2n), $r2n, "\nold", count($old), $old);

        $cd = '';      
        for ($f=0;$f<6;$f++) { # fRot: rotate one face
            $g = $f*9;
            $t2f = [$g+8=>'r2'];
            for ($t=0;$t<8;$t++) 
                $t2f[$g+$t] = $g + ($t + 6) % 8;
            for ($t=0;$t<8;$t+=2) {
                $t2f[$r2n[($t+2) % 8 + $g][2]] = $r2n[$t + $g][2];
                $t2f[$r2n[($t+3) % 8 + $g][1]] = $r2n[$t+1 + $g][1];
                $t2f[$r2n[($t+4) % 8 + $g][1]] = $r2n[($t+2) % 8 + $g][1];
            }
            $cd .= "\n    , $f => " . self::mkCode($t2f);    
        }
        $cd = "[" . substr($cd, 6) . "\n    ];";    
        # dbq("fRot $cd");
        self::$fRot = eval("return $cd");

        $tt = [];        # rRot: rotate the whole Rubik, around this face
        for ($f=1;$f<3;$f++) { 
            $g = $f*9;
            $t2f = [];
            for ($k=0;$k<8;$k+=2) {
                $t2f[$k + $g] = ($k+6) % 8 + $g;                                                        # current face
                $t2f[$k + 1 + $g] = ($k+7) % 8 + $g;
                $fG = ($fA = $r2n[($k+6) % 8 + $g][2]) - ($fC = $fA % 9);                               # neighbour face
                $fD = (($fQ = $r2n[($k+7) % 8 + $g][1]) - $fA + 8) % 8;
                (intdiv($fQ, 9) * 9 === $fG) or err("fA $fA not in same face as fQ $fQ");
                $tG = ($tA = $r2n[$k + $g][2]) - ($tC = $tA % 9);                               
                $tD = (($tQ = $r2n[$k+ 1 + $g][1]) - $tA + 8) % 8;
                (intdiv($tQ, 9) * 9 === $tG) or err("tA $tA not in same face as tQ $tQ");
                for ($j=0; $j<8; $j++)
                    $t2f[($tC + $j * $tD) % 8 + $tG] = ($fC + $j * $fD) % 8 + $fG;
                $t2f[8 + $tG] = 8 + $fG;
                $t2f[$tM = $r2n[($tC + 5 * $tD) % 8 + $tG][1]] = $r2n[($fC + 5 * $fD) % 8 + $fG][1];    # turn oposite face
                $t2f[$tN = $r2n[($tC + 6 * $tD) % 8 + $tG][2]] = $r2n[($fC + 6 * $fD) % 8 + $fG][2];        
                (intdiv($tM, 9) === intdiv($tN, 9)) or err("tM $tM not in same face as tN $tN");
            
            }
            $tt[intdiv($t2f[0] ?? 0, 9)] = $t2f;
            $tt[intdiv(($fl = array_flip($t2f))[0] ?? 0, 9)] = $fl;
        }
        $t2f = [];
        foreach ($ff = $tt[2] as $t => $f)
            $t2f[$t] = $ff[$f];
        $tt[intdiv($t2f[0] ?? 0, 9)] = $t2f;
        ksort($tt);
        # dbq("rRot tt", array_keys($tt), $tt);
        $cd = "0 => " . self::mkCode([]);
        foreach($tt as $f => $t2f)
            $cd .= "\n    , $f => " . self::mkCode($t2f);
        $cd = "[ $cd\n    ]";    
        # dbq("rRot $cd");
        self::$rRot = eval("return $cd;");     

        # rTrg target with center cells
        $t = [];
        for ($c = 0; $c < 8; $c++)
            $t[] = $r2n[$c];
        for ($c = 1; $c < 8; $c+=2)
            $t[] = $r2n[($a = $r2n[$c][1]) + ($a % 9 < 6 ? 2 : -6)];
        for ($c = 0; $c < 8; $c++)
            $t[] = $r2n[(10 - $c) % 8 + 45];
        self::$rTrg = array_map(fn ($v) => $r2n[min(...$v)], $t); # smallest index first
        self::$r2tr = [];
        foreach (self::$rTrg as $i => $n) {
            self::$r2tr[$n[0]] = $i;
            self::$r2tr[$n[1]] = $i;
            if(isset($n [2]))
                self::$r2tr[$n[2]] = $i;
        }
        # dbq('rTrg', self::$rTrg);
        # dbq('r2tr', self::$r2tr);
    }  # end config 

    public static function mkCode($t2f) {
        $t2f[54] = 54;
        ksort($t2f);
        $cO = 0;
        $fF = $l = -1;
        $fl = [];
        foreach ($t2f as $t => $f) {
            if ($f === $fF + $l) {
                $l++;
                continue;
            } 
            if ($fF >= 0) {
                $fl[] = [$fF, $l];
                $cO += $l;
            }
            if ($cO < $t) {
                $fl[] = [$cO, $t - $cO];
                $cO = $t;
            } elseif ($cO > $t) {
                err("cO $cO > tF $t");
            }
            if (['r2' => 2, 'r4' => 4, 'r6' => 6][$f] ?? false) {
                $fl[] = [$t, $f];
                $cO++;
                $fF = $l = -1;                        
            } else {
                $fF = $f;
                $l = 1;
            }
        }
        # dbQ("r", count($fl), $fl);

        $cL = 18;
        $cd = '';
        $cO = 0;
        foreach ($fl as list($f, $l)) {
            if ($cO >= $cL) {
                $cd .= "  # $cO\n     ";
                $cL = (intdiv($cO, 18) + 1) * 18;
            }
            if (is_int($l)) {
                $cd .= ($l > 1) ? " . substr(\$c, $f, $l)" : ($l === 1 ?  " . \$c[$f]" : err("l < 1 [$f, $l]"));
                $cO += $l;
            } elseif ($l === 'r2') { # }'$lx = ['r2' => 2, 'r4' => 4, 'r6' => 6][$l] ?? err("bad l $l")) {
                $cd .= " . (self::ROT8[\$c[$f]] ?? err(\"bad ROT8 c[$f] \$c[$f]\"))";
                $cO++;
            } else {
                err("bad fl [$f, $l]");
            }
        }
        $cd = "function (\$c) { return\n       " . substr($cd, 2) . "; }";
        # dbq("cd $cd");
        return $cd;
    } # end mkCode

/*
       foreach ($tE as $k0 => $v0) {
            foreach ($tE as $k1 => $v1) {
                foreach ($tE as $k2 => $v2) {
                    $v = ($k0 * 7) + $k1) *
                }
            }
        }

 */    
    public static function test($n) {
        $d = $c = '000000001111111111222222221333333331444444441555555551';
        static $rr =                      # runtime secs for    searcE searcF   ($) (const)   (call) comp19
            [ 0 => '013524405310355'                        #       63     31    33      31       32     63
            , 1 => '0135244053'                             #      147     72                     73
            , 2 => '01352440531035513445200022455533122' # needs 22 minutes (target 38!)
            , 3 => '000444331114445552223330555345511323551510011001115503444220'
            ];
        $nL = intdiv($n, 10);
        if (9 !== $nM = $n % 10) {
            $rrN = $nL === 0 ? $rr[$n] : str_pad('', $nL, $rr[$nM]);
        } else {
            $rrN = str_repeat($lC = intdiv($r = random_int(0, 17), 3), 1 + $r % 3);
            for($l = $nL ? $nL : 30; strlen($rrN) < $l;) {
                if ($lC === $aC = intdiv($r = random_int(0, 14), 3))
                    $aC = 5;
                $rrN .= str_repeat($lC = $aC, 1 + $r % 3);
            }
            $rrN = substr($rrN, 0, $l);
        }
        for ($i=0; $i < strlen($rrN); $i++) 
            $d = self::$fRot[(int) $rrN[$i]]($d);
        out("test search", self::SRCH, ", compress", SELF::COMP, ", testcase $n with", strlen($rrN), $rrN,  'rots testCase', $d);
        return $d;
    }

    public static function search($c, $d,  $t) {
        self::$tgt = $t;
        # dbq("tgt", self::$tgt);
        self::$vis = [$c => -1]; 
        self::$rC = 0;
        return self::searc1($c, $d);
    }

    public static function searc1($c, $d) {
        for ($r = 0; $r < 6; $r++) {
            $n = self::$fRot[$r]($c);
            self::$rC ++;
            # dbq("s" . self::$rC . "rot $r, $c ==> $n");
            if (isset(self::$vis[$n]))
                continue;
            $eq = true;
            foreach (self::$tgt as $t => $v)
                if ($n[$t] !== $v) {
                    $eq = false;
                    break;
                }
            self::$vis[$n] = $r;
            if ($eq)
                return self::$vis;
            if ($d>1 and $s = self::searc1($n, $d-1))
                return $s;
            (isset(self::$vis[$n])) or err("vis n lost");
            (array_pop(self::$vis) === $r) or err("mismatch pop");
            (isset(self::$vis[$n])) and err("pop did not unset n");
        }        
    }

    public static function searcC($c, $d,  $t) {
        self::$tgt = $t;
        self::$vis = [$c => -1]; 
        self::$rC = 0;
        return self::searc2($c, $d, 999);
    }

    public static function searc2($c, $d, $nR) {
        for ($r = 0; ($r === $nR) and $r++, $r < 6; $r++) {
            $n = $c;
            for ($r2=1; $r2<4; $r2++) {
                $n = self::$fRot[$r]($n);
                self::$rC ++;
                # dbq("s" . self::$rC . "rot $r, $c ==> $n");
                if (isset(self::$vis[$n]))
                    continue;
                $eq = true;
                foreach (self::$tgt as $t => $v)
                    if ($n[$t] !== $v) {
                        $eq = false;
                        break;
                    }
                self::$vis[$n] = $r*10+$r2;
                if ($eq)
                    return self::$vis;
                if ($d>1 and $s = self::searc2($n, $d-1, $r))
                    return $s;
                (isset(self::$vis[$n])) or err("vis n lost");
                (array_pop(self::$vis) === $r*10+$r2) or err("mismatch pop");
                (isset(self::$vis[$n])) and err("pop did not unset n");
            }
        }        
    }


    static function trgNxt($tt, $ix, $c) {
        foreach (self::$rTrg[$ix] as $t2) 
            $tt[$t2] = (string) intdiv($t2, 9);
        $u = $done = [];
        $tK = 0;
        $u = $done = [];
        $tK = 0;
        foreach ($tt as $tR => $tC) {
            if ($c[$tR] !== $tC) {
                $tK++;
                if (! isset($done[$tR])) {
                    foreach ($tN = self::$r2n[$tR] as $n)
                        $done[$n] = 1;
                    $u[] = self::$r2n[min(...$tN)];
                }
            }
        }
        $m = "target " . count($tt) . " colourCells, $tK changes";
        foreach ($u as $v) {
            $tCC = (1 << $tt[$v[0]]) | (1 << $tt[$v[1]]) | (isset($v[2]) ? (1 << $tt[$v[2]]) : 0);
            $m .= ", [$v[0] => " . $tt[$v[0]] . ", $v[1] => " . $tt[$v[1]] . (isset($v[2]) ? (", $v[2] => " . $tt[$v[2]]) : '') . "] $tCC";
            $cCC = 0;
            foreach(self::$r2n as $n)
                if ($tCC === $cCC = (1 << $c[$n[0]]) | (1 << $c[$n[1]]) | (isset($n[2]) ? (1 << $c[$n[2]]) : 0))
                    break;
            ($tCC === $cCC) or err("tCC !== cCC");
            $n = self::$r2n[min(...$n)];
            $m .= " <--< [$n[0] => " . $c[$n[0]] . ", $n[1] => " . $c[$n[1]] . (isset($n[2]) ? (", $n[2] => " . $c[$n[2]]) : '') . "]";
        }
    out("diff $m");
    return $tt;
    }

   static function solve($tst, $o, $stx=2) {
        $c = Rubik::test($tst);
        $o->slvB(self::SRCH . " stx=$stx");
        $rotCC = 0;
        while  (($r = new Rubik($c, $stx))->tTo > $r->tFr) {
            $r->rotL = $o->rotLTot - $rotCC;
            $o->srchB($r);
            $tB = time();
            $nn = $r->{Rubik::SRCH}();
            $tE = time();
            $o->srchE($nn);
            if (! is_array($nn))
                break;
            $d = $c;
            $o->rot('from', $c); 
            foreach($nn as $n) {
                for ($k = $n % 10; $k > 0; $k--)
                    $d = Rubik::$fRot[intdiv($n, 10) ]($d);
                $o->rot($n, $d); 
            } 
            $o->rotE();
            $rotCC += $o->rotCC;
            $c = $d;  
        }
        $o->slvE($c);
    }

    public $fA, $tA, $rotC, $rotL, $back, $tBack = 0, $tSrch = 0; 

    public function __construct(public $from, $tP) { # construct an instance for Rubiik colours $from and target rTrg[0...$ty]
        $this->rotL = (int) 1e8;
        $t = $f = str_repeat('f', 54);  # start with from to all unknown
        $mm = '';
        $tc = -1;
        $ty = count(self::$rTrg) - 1;
        $this->tFr = 99;
        $this->tFc = 99;
        foreach (self::$rTrg as $tx => $n) {
            $h2 = isset($n[2]);
            $tc += 2 + (int) $h2;
            $t[$n[0]] = $c0 = (string) intdiv($n[0], 9);    # set the 2 or 3 t colours
            $t[$n[1]] = $c1 = (string) intdiv($n[1], 9);
            $h2 and ($t[$n[2]] = $c2 = (string) intdiv($n[2], 9));
            if ($from[$n[0]] === $c0 and $from[$n[1]] === $c1 and ((! $h2) or $from[$n[2]] === $c2)) { # from ist already correct, colour from
                $f[$n[0]] = $c0;
                $f[$n[1]] = $c1;
                $h2 and ($f[$n[2]] = $c2);
            } else { 
                if ($this->tFr > $tx) {
                    $this->tFr = $tx - 1;
                    $this->tFc = $tc - 2 - (int) $h2;
                    $ty = min($ty , $tx+$tP - 1);
                }
                $tz ??= $tx;                                                                                   # from has other colours
                $tCC = (1 << $c0) | (1 << $c1) | ($h2 ? (1 << $c2) : 0);                                # search the needed cubelet in from
                foreach(self::$r2n as $q) 
                    if ($tCC === $fCC = (1 << $from[$q[0]]) | (1 << $from[$q[1]]) | (isset($q[2]) ? (1 << $from[$q[2]]) : 0)) 
                        break;                                                                          # this is the needed cubelt
                ($tCC === $fCC) or err("tCC $tCC !== fCC $fCC c $c0 $c1 $c2", $n);
                $f[$q[0]] = $from[$q[0]];                                                               # colour needed cubelet in from
                $f[$q[1]] = $from[$q[1]];
                $h2 and ($f[$q[2]] = $from[$q[2]]);
                $q = self::$r2n[min(...$q)];
                $mm .= ", [$n[0]=$c0,$n[1]=$c1" . ($h2 ? ",$n[2]=$c2" : '') . "] << [$q[0]=" 
                        . $from[$q[0]] . ",$q[1]=" . $from[$q[1]] . ($h2 ? (",$q[2]=" . $from[$q[2]]) : '') . "]";
            }
            if ($tx >= $ty)
                break;
        }
        $this->tTo = $tx;
        $this->tTc = $tc;
        $this->tA = $t;
        $this->fA = $f;
        $this->cm1 = "from $this->tFr/$this->tFc to target $this->tTo/$this->tTc cublets/colors";
        $this->cm2 = "changes" . substr($mm, 1);
        #dbq("\nfrom = $from\nfA   = $f\ntA   = $t");
    }

    public function searcD() {
        $bk = [$this->tA => 90];
        $bkC = max(2, min(self::$backMax, round($this->tTc / 7.2)));
        $this->rotC = 0;
        $this->tBack = -hrtime(true);
        $mL = $m0 = time() + 3;
        $tB = hrtime(true);
        $tRC = $this->rotC;
        $tBK = count($bk);
        for ($bx=0; $bx<$bkC; $bx++) {
            $bz = 100 + $by = $bx * 100;
            foreach($bk as $c => $d) {
                if ($d < $by or $d > $bz)
                    continue;
                $rNo = intdiv($d, 10) % 10;
                for ($r = 0; ($r === $rNo) and $r++, $r < 6; $r++) {
                    for ($c2=$c, $r2=1; $r2<4; $r2++) {
                        $c2 = self::$fRot[$r]($c2);
                        $this->rotC++;
                        $bk[$c2] ??= $bz + 10*$r + $r2;
                    }
                }
                if ($mL < time()) {
                    if (memory_get_usage(true) > self::MEMMAX) {
                        out(sprintf("back: memLimit %.2e exceeded by usage %.2e, no longer expanding back", self::MEMMAX, memory_get_usage(true)));
                        break;
                    }
                    $mL +=2;
                    $tN = hrtime(true);
                    echo "*** rotC " . round(($this->rotC - $tRC) / ($tN-$tB) * 1e9) . ", bk " . round((count($bk) - $tBK) / ($tN-$tB) * 1e9) . sprintf(' memory %7.2e real %7.2e,', memory_get_usage(), memory_get_usage(true)) . date(' Y-m-d\TH:i:s') . "    \r";
                    $tB = $tN;
                    $tRC = $this->rotC;
                    $tBK = count($bk);
                }
            }
            $tN = hrtime(true);
            if ($mL > $m0)
                echo "\n... rotC " . round(($this->rotC - $tRC) / ($tN-$tB) * 1e9) . ", bk " . round((count($bk) - $tBK) / ($tN-$tB) * 1e9) . sprintf(' memory %7.2e real %7.2e,', memory_get_usage(), memory_get_usage(true)) . date(' Y-m-d\TH:i:s') . "    \n";
            dbg1("bx $bx, rotC $this->rotC, bk", count($bk), sprintf('memory %7.2e real %7.2e,', memory_get_usage(), memory_get_usage(true)), date('Y-m-d\TH:i:s'));
            if (isset($bk[$this->fA])) 
                break;
        }
        $this->tBack += hrtime(true);
        if (isset($bk[$this->fA])) {
            $stps = [$this->fA];
        } else {
            $this->back = $bk;
            $this->tSrch -= hrtime(true);
            for ($dx=1; $dx < self::$depthMax - $bkC; $dx++) {
                $stps = $this->searcDR($this->fA, $dx, 9);
                out("searcDR $dx,",  sprintf('%7.2e =', $this->rotC), $this->rotC, "tries,", date('Y-m-d\TH:i:s'));
                if (! is_null($stps))
                    break;
            }
            $this->tSrch += hrtime(true);
        }
        # dbq('stps 1', $stps);
        if (is_null($stps))
            return null;

        $stps = array_reverse($stps);
        $g = array_pop($stps);
        # dbq('stps 2', $stps);
        # dbq("to back   $g");
        while (90 !== $r = ($bk[$g] ?? err("missing in back $g")) % 100) {
            $stps[] = $r += 4 - ($r % 10) * 2;
            for ($k = $r % 10; $k > 0; $k--)
                $g = self::$fRot[intdiv($r, 10)]($g);
            # dbq("back $r > $g");
        }
        dbq('return stps', count($stps), $stps);
        return $stps;
    } # end seachD

    public function searcE() {
        $bk = [$this->tA => 90];
        $bx = $sx = 0;
        $bkC = max(2, min(self::$backMax, round($this->tTc / 7.2)));
        $bMore = true;
        $bTi = $this->tBack = $sTi = $this->tSrch = 0;
        $this->rotC = 0;
        while (true) {
            if ($bx + $sx >= self::$depthMax)
                return null;
            $tL = 3e9 + $tB = hrtime(true);
            if ($bx < $bkC or ($bMore and $bTi < $sTi * 0.5)) { # expand back
                $laBa = true;
                $bz = 100 + $by = $bx * 100;
                $bx++;
                foreach($bk as $c => $d) {
                    if ($d < $by or $d > $bz)
                        continue;
                    $rNo = intdiv($d, 10) % 10;
                    for ($r = 0; ($r === $rNo) and $r++, $r < 6; $r++) {
                        for ($c2=$c, $r2=1; $r2<4; $r2++) {
                            $c2 = self::$fRot[$r]($c2);
                            $this->rotC++;
                            $bk[$c2] ??= $bz + 10*$r + $r2;
                        }
                    }
                    if ($tL < hrtime(true)) {
                        if (memory_get_usage(true) > self::MEMMAX) {
                            out(sprintf("back: memLimit %.2e exceeded by usage %.2e, no longer expanding back", self::MEMMAX, memory_get_usage(true)));
                            $bMore = false;
                            break;
                        }
                        $tL = hrtime(true) + 2e9;
                    }
                }
                $this->tBack += $bTi = ($tN = hrtime(true)) - $tB;
                dbg1("bx $bx, rotC $this->rotC, bk", count($bk), sprintf('memory %7.2e real %7.2e,', memory_get_usage(), memory_get_usage(true)), date('Y-m-d\TH:i:s'));
                if (isset($bk[$this->fA])) {
                    $stps = [$this->fA];
                    break;
                }
                $this->back = $bk;
            } else {
                $laBa ? ($laBa = false) : $sx++; 
                $stps = $this->searcDR($this->fA, $sx, 9);
                # dbq("searcDR $sx,",  sprintf('%7.2e =', $this->rotC), $this->rotC, "tries,", date('Y-m-d\TH:i:s'));
                $this->tSrch += $sTi = hrtime(true) - $tB;
                if (! is_null($stps))
                    break;
            }
        }
        $stps = array_reverse($stps);
        $g = array_pop($stps);
        # dbq('stps 2', $stps);
        # dbq("to back   $g");
        while (90 !== $r = ($bk[$g] ?? err("missing in back $g")) % 100) {
            $stps[] = $r += 4 - ($r % 10) * 2;
            for ($k = $r % 10; $k > 0; $k--)
                $g = self::$fRot[intdiv($r, 10)]($g);
            # dbq("back $r > $g");
        }
        # dbq('return stps', count($stps), $stps);
        return $stps;
    } # end seachE

    public function searcDR($c, $d, $nR) {
        for ($r = 0; $r < 6; $r++) {
            if ($r === $nR)
                continue;
            $n = $c;
            for ($r2=1; $r2<4; $r2++) {
                $n = self::$fRot[$r]($n);
                $this->rotC ++;
                # dbq("s" . self::$rC . "rot $r, $c ==> $n");
                if (isset($this->back[$n]))
                    return [$n, $r *10 + $r2];
                if ($d>1 and ($s = $this->searcDR($n, $d-1, $r))) {
                    array_push($s, $r *10 + $r2);
                    return $s;
                }
            }
        }
    }

   public function searcF() {
        $bk = [(self::COMP)($this->tA) => 90];
        $bx = $sx = 0;
        $bkC = max(2, min(self::$backMax, round($this->tTc / 7.2)));
        $bMore = true;
        $bTi = $this->tBack = $sTi = $this->tSrch = 0;
        $this->rotC = 0;
        while (true) {
            if ($bx + $sx >= self::$depthMax)
                return null;
            $tL = 3e9 + $tB = hrtime(true);
            if ($bx < $bkC or ($bMore and $bTi < $sTi * 0.5)) { # expand back
                $laBa = true;
                $bz = 100 + $by = $bx * 100;
                $bx++;
                foreach($bk as $c => $d) {
                    if ($d < $by or $d > $bz)
                        continue;
                    $rNo = intdiv($d, 10) % 10;
                    $c = (self::DECO)($c);
                    for ($r = 0; ($r === $rNo) and $r++, $r < 6; $r++) {
                        for ($c2=$c, $r2=1; $r2<4; $r2++) {
                            $c2 = self::$fRot[$r]($c2);
                            $this->rotC++;
                            $bk[(self::COMP)($c2)] ??= $bz + 10*$r + $r2;
                        }
                    }
                    if ($tL < $tN = hrtime(true)) {
                        if (memory_get_usage(true) > self::MEMMAX) {
                            out(sprintf("back: memLimit %.2e exceeded by usage %.2e, no longer expanding back", self::MEMMAX, memory_get_usage(true)));
                            $laBa = $bMore = false;
                            break;
                        }
                        $tL = $tN + 2e9;
                    }
                }
                $this->tBack += $bTi = ($tN = hrtime(true)) - $tB;
                # dbq("bx $bx, rotC $this->rotC, bk", count($bk), sprintf('memory %7.2e real %7.2e,', memory_get_usage(), memory_get_usage(true)), date('Y-m-d\TH:i:s'));
                if (isset($bk[(self::COMP)($this->fA)])) {
                    $stps = [$this->fA];
                    break;
                }
                $this->back = $bk;
            } else {
                $laBa ? ($laBa = false) : $sx++; 
                $stps = $this->searcFR($this->fA, $sx, 9);
                # dbq("searcFR $bx " . count($bk) . "/$sx,",  sprintf('%7.2e =', $this->rotC), $this->rotC, "tries,", date('Y-m-d\TH:i:s'));
                $this->tSrch += $sTi = hrtime(true) - $tB;
                if (! is_null($stps))
                    break;
            }
        }
        $stps = array_reverse($stps);
        $g = array_pop($stps);
        # dbq('stps 2', $stps);
        # dbq("to back   $g");
        while (90 !== $r = ($bk[(self::COMP)($g)] ?? err("missing in back $g")) % 100) {
            $stps[] = $r += 4 - ($r % 10) * 2;
            for ($k = $r % 10; $k > 0; $k--)
                $g = self::$fRot[intdiv($r, 10)]($g);
            # dbq("back $r > $g");
        }
        $this->bkI = sprintf('back %d, %7.2e sz, mem %7.2e, r %7.2e', $bx, count($bk), memory_get_usage(), memory_get_usage(true));
        #out('searchE', count($stps), 'stps', $stps);
        return $stps;
    } # end searchE

    public function searcFR($c, $d, $nR) {
        for ($r = 0; $r < 6; $r++) {
            if ($r == $nR)
                continue;
            $n = $c;
            for ($r2=1; $r2<4; $r2++) {
                $n = self::$fRot[$r]($n);
                $this->rotC ++;
                # dbq("s" . self::$rC . "rot $r, $c ==> $n");
                if (isset($this->back[(self::COMP)($n)]))
                    return [$n, $r *10 + $r2];
                if ($d>1 and ($s = $this->searcFR($n, $d-1, $r))) {
                    array_push($s, $r *10 + $r2);
                    return $s;
                }
            }
        }        
    }

  public function searcG() {
        $this->back = [(self::COMP)($this->tA) => 90];
        $bx = $sx = 0;
        $bkC = max(2, min(self::$backMax, round($this->tTc / 7.2)));
        $bMore = true;
        $bTi = $this->tBack = $sTi = $this->tSrch = 0;
        $this->rotC = 0;
        do {
            if ($bx + $sx >= self::$depthMax)
                return null;
            $tB = hrtime(true);
            if ($bx < $bkC or ($bMore and $bTi < $sTi * 0.5)) { # expand back
                $stps = $this->searcGBack(++$bx);
                 $this->tBack += $bTi = hrtime(true) - $tB;
                $laBa = true;
                dbg1("bx $bx, rotC $this->rotC, bk", count($this->back), sprintf('memory %7.2e real %7.2e,', memory_get_usage(), memory_get_usage(true)), date('Y-m-d\TH:i:s'));
            } else {
                $laBa ? ($laBa = false) : $sx++; 
                $stps = $this->searcGR($this->fA, $sx, 9);
                $this->tSrch += $sTi = hrtime(true) - $tB;
            }
        } while (! $stps);
        $this->bkI = sprintf('back %d, %7.2e sz, mem %7.2e, r %7.2e', $bx, count($this->back), memory_get_usage(), memory_get_usage(true));
        if (is_string($stps))
            return $stps;
        $stps = array_reverse($stps);
        $g = array_pop($stps);
        # dbq('stps 2', $stps);
        # dbq("to back   $g");
        while (90 !== $r = ($this->back[(self::COMP)($g)] ?? err("missing in back $g")) % 100) {
            $stps[] = $r += 4 - ($r % 10) * 2;
            for ($k = $r % 10; $k > 0; $k--)
                $g = self::$fRot[intdiv($r, 10)]($g);
            # dbq("back $r > $g");
        }
        # dbq('searchG', count($stps), 'stps', $stps);
        return $stps;
    } # end searchG

    public function searcGR($c, $d, $nR) {
        for ($r = 0; $r < 6; $r++) {
            if ($r == $nR)
                continue;
            if ($this->rotC > $this->rotL)
                return "too many tries $this->rotC > $this->rotL"; 
            $n = $c;
            for ($r2=1; $r2<4; $r2++) {
                $n = self::$fRot[$r]($n);
                $this->rotC ++;
                # dbq("s" . self::$rC . "rot $r, $c ==> $n");
                if (isset($this->back[(self::COMP)($n)]))
                    return [$n, $r *10 + $r2];
                if ($d>1 and ($s = $this->searcGR($n, $d-1, $r))) {
                    if (is_array($s))
                        $s[] = $r *10 + $r2;
                    return $s;
                }
            }
        }        
    } # end searchGR

    public function searcGBack($bx) {
 #  $tL = 3e9 + $tB = hrtime(true);
 #       $laBa = true;
        $by = -100 + $bz = $bx * 100;
        foreach($this->back as $c => $d) {
            if ($d < $by)
                continue;
            if ($d >= $bz)
                err("searcGBack bx $bx but d $dx");
            $rNo = intdiv($d, 10) % 10;
            $c = (self::DECO)($c);
            if ($this->rotC > $this->rotL)
                return "too many tries $this->rotC > $this->rotL"; 
            for ($r = 0; $r < 6; $r++) {
                if ($r === $rNo)
                    continue;
                for ($c2=$c, $r2=1; $r2<4; $r2++) {
                    $c2 = self::$fRot[$r]($c2);
                    $this->rotC++;
                    $this->back[(self::COMP)($c2)] ??= $bz + 10*$r + $r2;
                }
            }
/*            if ($tL < $tN = hrtime(true)) {
                if (memory_get_usage(true) > self::MEMMAX) {
                    out(sprintf("back: memLimit %.2e exceeded by usage %.2e, no longer expanding back", self::MEMMAX, memory_get_usage(true)));
                    $laBa = $bMore = false;
                    break;
                }
                $tL = $tN + 2e9;
            }
*/      }
        return isset($this->back[(self::COMP)($this->fA)]) ? [$this->fA] : null;
    } # end searcGBack
} # end class Rubik

class RubikOutCLI {

    static $line;

    public static function ini () {
        self::$line = # format Rubik human readable
            [ 10 => fn($c, $g) => $c[$g+6] . $c[$g+5] . $c[$g+4]    # top line of face
            , 11 => fn($c, $g) => $c[$g+7] . $c[$g+8] . $c[$g+3]    # middle line of face
            , 12 => fn($c, $g) => substr($c, $g , 3)                # bottom line of face
            ,  0 => fn($c) => '    ' . self::$line[10]($c, 27) . '        '     # line 0 to 8 of the unwinding of the 6 faces
            ,  1 => fn($c) => '    ' . self::$line[11]($c, 27) . '        '
            ,  2 => fn($c) => '    ' . self::$line[12]($c, 27) . '        '
            ,  3 => fn($c) => ($l = self::$line[10])($c, 36) . ' ' . $l($c, 0) . ' ' . $l($c, 18) . ' ' . $l($c, 45)
            ,  4 => fn($c) => ($l = self::$line[11])($c, 36) . ' ' . $l($c, 0) . ' ' . $l($c, 18) . ' ' . $l($c, 45)
            ,  5 => fn($c) => ($l = self::$line[12])($c, 36) . ' ' . $l($c, 0) . ' ' . $l($c, 18) . ' ' . $l($c, 45)
            ,  6 => fn($c) => '    ' . self::$line[10]($c, 9) . '        '
            ,  7 => fn($c) => '    ' . self::$line[11]($c, 9) . '        '
            ,  8 => fn($c) => '    ' . self::$line[12]($c, 9) . '        '
            , 20 => fn($c) => self::$line[10]($c, 27) . ' / ' . self::$line[3]($c) . ' / ' . self::$line[10]($c, 9)     # line 0 to 2 of the linearization of the 6 faces
            , 21 => fn($c) => self::$line[11]($c, 27) . ' / ' . self::$line[4]($c) . ' / ' . self::$line[11]($c, 9)     
            , 22 => fn($c) => self::$line[12]($c, 27) . ' / ' . self::$line[5]($c) . ' / ' . self::$line[12]($c, 9)     
            ];
    }
       
    public function __construct() { 
        if (! self::$line)
            self::ini();
    }

    public function out($t, $c) { # output text $t and Rubik $c 
        out('--- ' . (is_int($t) ? "rot $t" : $t) . ' ---');
        for ($l=0; $l<9; $l++)
            out(self::$line[$l]($c));
    }

    public function slvB2() {}
    public function slvE2() {}
    public function rot2($t, $c) { $this->out($t, $c); }
    public function rotE2() {}

    public function linL($c) {
        for ($l=20; $l<23; $l++)
            out(self::$line[$l]($c));
    }

    public function linR($c, $r) {
        $d = self::$rRot[$r]($c);
        for ($l=0; $l<9; $l++)
            out(self::$line[$l]($c) . " -r$r- ". self::$line[$l]($d));
    }
} # end class RubikOutCli

class RubikOutHTML {
   const JSFUN = <<<JS
    <script type="text/javascript">
        function rubik(ctx, tlX, tlY, cc) {
            const s = 12, w=1;
            const face = [[3*s+4*w,3*s+4*w], [3*s+4*w, 6*s+6*w], [6*s+6*w,3*s+4*w],[3*s+4*w, 2*w], [2*w, 3*s+4*w], [9*s+8*w, 3*s+4*w]];
            const cell = [[0,2], [1,2], [2,2],[2,1], [2,0], [1, 0], [0,0], [0,1], [1,1]];
            const col = ['red', 'yellow', 'blue', 'white', 'green', 'orange'];
            const coW = ['black', 'black', 'white', 'black', 'white', 'black'];

            const orient = ['?', "\u{2193}", '?', "\u{2192}", '?', "\u{2191}", '?', "\u{2190}", '?'];
            ctx.lineWidth = 2*w;
            ctx.strokeType = "black";
            ctx.strokeRect(tlX+w, tlY+3*s+3*w, 12*s+8*w, 3*s+2*w);
            ctx.strokeRect(tlX+3*s+3*w, tlY+w, 3*s+2*w, 9*s+6*w);
            ctx.strokeRect(tlX+9*s+7*w, tlY+3*s+3*w, 3*s+2*w, 3*s+2*w);
            for (let f = 0; f < 6; f++) {
                f9 = f * 9;
                x = tlX + face[f][0];
                y = tlY + face[f][1];
                for (let i = 0; i < 9; i++) {
                    ctx.fillStyle = col[(i==8 ? f : cc[f9+i])];
                    ctx.fillRect(x+s*cell[i][0], y+s*cell[i][1], s, s);
                }
                ctx.fillStyle = coW[f];
                ctx.font = "15px Arial ExtraBold";
                ctx.fillText(orient[cc[f9+8]], x+s*cell[8][0], y+s+s*cell[8][1], s);
            }
        }  
    </script>
    JS;
 
    public function out($t, $c) { # output text $t and Rubik $c 
        out((is_int($t) ? ('<span style="background-color: ' . ($k = Rubik::COL[intdiv($t,10)]) . ';color: ' . Rubik::COW[intdiv($t,10)] . ";\">rot $t = $k" . Rubik::ROT[$t%10]) : $t) 
                           . '</span><br><canvas id="rubikc' . $this->canC . '" width="160" height="120"></canvas>');
        $this->canIni .= "\n\t\trubik(document.getElementById('rubikc$this->canC').getContext('2d'), 2, 2, '$c');";
        $this->canC++;
    }

   private $canC = 0, $canIni = '';

   public function slvB2() {
        outTb();
        out(self::JSFUN, str_repeat('&nbsp;', 45));
    }

    public function slvE2() {
        outTBEnd();
    }

   
   public function rot2($t, $c) { # output text $t and Rubik $c for the rotation table
        outTD();
        $this->out($t, $c);
    }

    public function rotE2() {
        out("<script type=\"text/javascript\">$this->canIni\n</script>");
        $this->canIni = '';
        outTR();
    }
} # end class RubikOutHTML

class RubikCnt {
    public $rubik, $tSlvB, $slvInf, $tSrchB, $rotCC, $rotLTot, $rotNN, $rotInf, $err, $tSr, $tBa;

    public function __construct() {
        $this->rotLTot = (int) 1e8;
    }

    public function slvB($i) {
        $this->tSlvB = time();
        $this->slvInf = $i;
        $this->rotCC = $this->rotNN = $this->tSr = $this->tBa = $this->err = 0;
    }

    public function slvE($c) {}

    public function srchB($r) {
        $this->rubik = $r;
        $this->tSRchB = time();
    }

    public function srchE($rs) { # after return from search
        $this->rotCC += $this->rubik->rotC;
        $this->tSr += $this->rubik->tSrch;
        $this->tBa += $this->rubik->tBack;
        if (is_array($rs)) {
            $this->rotNN += count($rs);
            $this->rotInf = 'search found ' . count($rs) . ' rots ' . a2str($rs);
        } elseif (! $this->err) {
            $this->rotInf = $this->err = 'not found ' . ($rs ?? 'returned null');
        }
    }

    public function rot($t, $c) {} # one rotation $t to rubik $c
    public function rotE() {} # after last rotation of one search
} # end class RubikCnt

class RubikOut extends RubikCnt {
    public function __construct() { 
        $this->o = OutHtml ? new RubikOutHtml : new RubikOutCli;
    }

    public function out($t, $c) { # output text $t and Rubik $c 
        $this->o->out($t, $c);
    }

    public function slvB($i) {
        parent::slvB($i);
        $this->o->slvB2();
    }

    public function slvE($c) {
        parent::slvE($c);
        $tE = time();
        out($this->err ? $this->err : "solved $this->rotNN rots,", sprintf('%7.2e tries,', $this->rotCC));
        out($tE-$this->tSlvB, "secs,", sprintf('%.3f back, %.3f search at', $this->tBa/1e9, $this->tSr/1e9, 3)); #, date('Y-m-d\TH:i:s', $tE));
        if (! $this->err) 
            $this->o->rot2('end', $c);
        $this->o->rotE2();
        $this->o->slvE2();
    }

    public function srchB($r) {
        parent::srchB($r);
        out($r->cm1);
        out($r->cm2);
    }

    public function srchE($rs) { # after return from search
        parent::srchE($rs);
        $tE = time();
        out($this->err ? $this->err : "solved {$this->rubik->tTo}/{$this->rubik->tTc}", sprintf(', with %7.2e tries', $this->rubik->rotC));
        out("$this->slvInf $this->rotInf");
        out($this->rubik->bkI);
        out($tE-$this->tSRchB, "secs,", $tE-$this->tSlvB, "tot  at", date('Y-m-d\TH:i:s', $tE));
    }

    public function rot($t, $c) { $this->o->rot2($t, $c); } # one rot
    public function rotE() { $this->o->rotE2(); } # after last rot
} # end class RubikOut

class RubikOutSlv extends RubikCnt {
    private $m1Inf, $m1RotC;

    public function slvB($i) {
        parent::slvB($i);
        $this->m1RotC = $m1Inf = -1;
    }

    public function srchE($rs) { # after return from search
        parent::srchE($rs);
        if ($this->rubik->rotC > $this->m1RotC) {
            $this->m1RotC = ($r = $this->rubik)->rotC;
            $this->m1Inf = [$r->cm1, $r->cm2, $this->rotInf, [sprintf('%7.2e tries', $r->rotC), ", $r->bkI in", time()-$this->tSRchB, "secs"]];
        }
    }
    public function slvE($c) {
        parent::slvE($c);
        out($this->slvInf, $this->err ? $this->err : "solved" , "$this->rotNN rots,", sprintf('%7.2e tries,', $this->rotCC));
        out(time() - $this->tSlvB, "secs,", sprintf('%.3f back, %.3f search at', $this->tBa/1e9, $this->tSr/1e9, 3)); 
        foreach ($this->m1Inf as $i)
            is_array($i) ? out(...$i) : out($i);
    }
} # end class RubikOutSlv

outBegin();
Rubik::config();

if (0) {
    $r = 'abcdefgh1ijklmnop1qrstuvwx1yzABCDEF1GHIJKLMN1OPQRSTUV1';
    Rubik::linO($r);
    for ($x=0; $x<6; $x++) {
        out ("\nrot $x");
        $n = $r;
        for ($y=1; $y<5; $y++) {
            $n = Rubik::$fRot[$x]($n);
            out ("$y n $n", strlen($n));
            #Rubik::linO($n);
            Rubik::linR($n, $x);
        }
        ($n === $r) or err("n !== r"); 
    }
    $t = str_pad('', 54, 'f');
    foreach (Rubik::$rTrg as $x => $cc)
        foreach ($cc as $c)
            $t[$c] = '0123456789abcdefghijklmnopqrstuvwxyz'[$x];
    out("rTrg", $t);
    Rubik::linO($t);
}
if (0) {
    $srch = 'searcC';
    $c = Rubik::test(0);
    $tXX=0;
    $tt = [];
    $tB00 = time();
    while ($tt = Rubik::trgNxt($tt, $tXX++, $c)) {
        $tc = count($tt);
        $tB = time();
        out("the problem for $tc target colourCells ", date('Y-m-d\TH:i:s', $tB));
        Rubik::linO($c);
        for ($d=1; $d<15; $d++) {
            if (! $s = Rubik::$srch($c, $d, $tt)) { 
                out("search $d notfound", sprintf('%7.2e =', Rubik::$rC), Rubik::$rC, date('Y-m-d\TH:i:s'));
            } else {
               $tE = time();
                out("search $d for $tc targets found", count($s) - 1, "rots,", sprintf('%7.2e =', Rubik::$rC), Rubik::$rC, "tries," ,$tE-$tB, "secs,", $tE-$tB00, "tot  at", date('Y-m-d\TH:i:s', $tE));
                outTB();
                outTCF('r');
                outTRD(...range(0, count($s)-1));
                outTRD(...array_values($s));
                outTR();
                foreach($s as $v => $vRR) {
                    $w = 0;
                    foreach ($tt as $a => $c)
                        if ($c !== $v[$a])
                            $w++;
                    outTD($w);
                }
                outTBEnd();
                foreach($s as $n => $r) {
                    out("--- rot $r ------");
                    Rubik::linO($n);
                }
                $c = array_key_last($s);
                continue 2;
            }
        }
    err("search not found for $tc, rC", Rubik::$rC);
    }
}
if (1) {
    $o = new RubikOut; 
    $o->rotLTot = 1e9;
    Rubik::solve(0, $o, 2);
/*    foreach ([0,1, 909] as $t ) {
        outH("test $t");
        $o = new RubikOutSlv; 
        foreach( [1,2,3,4,5,7,10, 20] as $x) {
            out("rotCC $o->rotCC, lim $o->rotLTot");
            Rubik::solve($t, $o, $x);
            $o->rotLTot = min($o->rotLTot, 2 * $o->rotCC);
        }
    } */
   # for($x=1; $x<=4; $x++)
    #    Rubik::solve(1, $o, $x); */
}
if (0) {
    $c = Rubik::test(3);
    $tB00 = time();
    $rotCC = $rotNN = $tSr = $tBa = 0;
    Rubik::linIO('b', '');
    while  (($r = new Rubik($c, 2))->tTo > $r->tFr) {
        $tB = time();
        $nn = $r->{Rubik::SRCH}();
        $tE = time();
        if (is_null($nn))
            err("count not solve $r->tTo/$r->tTc targets,", $tE-$tB, "secs,", $tE-$tB00, "tot  at", date('Y-m-d\TH:i:s', $tE));
        out("solved $r->tTo/$r->tTc in", count($nn), "rots,", sprintf('%7.2e tries', $r->rotC));
        out($r->bkI);
        out($tE-$tB, "secs,", $tE-$tB00, "tot  at", date('Y-m-d\TH:i:s', $tE));
        Rubik::linIO('from', $d = $c);
        foreach($nn as $n) {
            $n = $n % 100;
            for ($k = $n % 10; $k > 0; $k--)
                $d = Rubik::$fRot[intdiv($n, 10) ]($d);
            Rubik::linIO($n, $d);
        } 
        Rubik::linIO('r', '');
        $c = $d;  
        $rotCC += $r->rotC;
        $rotNN += count($nn);
        $tSr += $r->tSrch;
        $tBa += $r->tBack;
    }
    out("solved $rotNN rots,", sprintf('%7.2e tries,', $rotCC));
    out($tE-$tB00, "secs,", sprintf('%.3f back, %.3f search at', $tBa/1e9, $tSr/1e9, 3)); #, date('Y-m-d\TH:i:s', $tE));
    Rubik::linIO('end', $c);
    Rubik::linIO('r', '');
    Rubik::linIO('e', '');
}

err('tst end');
Rubik::linO($c);
#err(config());
# echo dechex($f(0x123436789)) . "\n";
/*
for ($i=0; $i<6; $i++)
    rsh($i, $r);
exit();

foreach (Rubik::$rRot as $fa => $fu) {
    out("rRot $fa");
    Rubik::linO($fu($r));
}

err('endTst');
*/
Rubik::linO($r);

for ($x=0; $x<6; $x++) {
    out ("\nrot $x");
    $n = $r;
    for ($y=1; $y<5; $y++) {
        $n = Rubik::$fRot[$x]($n);
        out ("$y n $n", strlen($n));
        Rubik::linL($n);
        Rubik::linR($n, $x);
    }
    ($n === $r) or err("n !== r"); 
}
exit();

$n =$rot[0]($r);
ll($n);
for ($x=1; $x<7; $x++) {
    out("--- rot $x");
    for ($y=0; $y<4; $y++) {
        $rot[$x]();
        ll();
    }
}