Information about my approach:
It gives a lot of information and source code. Code is not directly compilable, it needs some code fixes, someone with C++ experience can fix it easily. The idea is learn from it, not simply C&P.
Key points are:
-Use Merge a lot. This reduces a number without breaking anything.
-Use exhaustive permutation of ending moves on good candidates. Try to reach a 0 by itself or be able to merge on another intermediate value on a neighbour. The problem is that permutations are really expensive.
-Priorize performance on random move generation. Minimize smart moves at that point, add them on the Mutation part, choosing more prospectable Moves to mutate. Performance per thread matters, a CPU running 200% faster than other 4xCPUs solved 90% of the 900+ levels.
-A physical corei7 with 6 cores can do 10threads without bloating the whole PC (you can do normal work while running the solver), vCPUs on the cloud are usually worse. With 6 threads you can even play 3D games at 144hz without noticeable slowdown.
-Use good mutators. Not only randoms, but ending points, incompletes numbers, etc… One mutator I have that is based on ending moves affecting incomplete Strategies achieved 50% of good candidates.
-Keep a pool of good approximations (=low remaining numbers), no matter the score, allow worse candidates as long as remaining numbers are below a threshold. LAHC failed a lot on endgame, it lost many near solutions because my Mutators were unable to exit a local minimum, and LAHC hasn’t any further protection against that, you can only reset the whole search.
-In that pool, ensure that candidates are unique. Don’t accept repeated candidates with the same remaining numbers, replace equals if the new candidate has a better score. Check/remove supersets (a candidate with the same remaining numbers than an existing candidate but additional remainings).