summaryrefslogtreecommitdiff
path: root/core/src/jvmShared/kotlin/coffee/liz/rookery/scramble/TnoodleScrambler.kt
blob: 442717733e26a0989110d848ea72393275251acb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package coffee.liz.rookery.scramble

import coffee.liz.rookery.model.Event
import org.worldcubeassociation.tnoodle.scrambles.PuzzleRegistry
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.Future

/**
 * WCA-official random-state scrambles from tnoodle-lib, with one scramble generated ahead per event
 * so pressing "next" is instant even for 4x4 (whose tables take a few seconds to build).
 */
class TnoodleScrambler : Scrambler {
    private val pool = Executors.newSingleThreadExecutor { r -> Thread(r, "scrambler").apply { isDaemon = true; priority = Thread.MIN_PRIORITY } }
    private val ahead = ConcurrentHashMap<Event, Future<String>>()

    private fun registry(e: Event): PuzzleRegistry = when (e) {
        Event.E333, Event.E333OH -> PuzzleRegistry.THREE
        Event.E222 -> PuzzleRegistry.TWO
        Event.E444 -> PuzzleRegistry.FOUR
        Event.E555 -> PuzzleRegistry.FIVE
        Event.E666 -> PuzzleRegistry.SIX
        Event.E777 -> PuzzleRegistry.SEVEN
        Event.E333BF, Event.E333MBF -> PuzzleRegistry.THREE_NI
        Event.E444BF -> PuzzleRegistry.FOUR_NI
        Event.E555BF -> PuzzleRegistry.FIVE_NI
        Event.E333FM -> PuzzleRegistry.THREE_FM
        Event.CLOCK -> PuzzleRegistry.CLOCK
        Event.MINX -> PuzzleRegistry.MEGA
        Event.PYRAM -> PuzzleRegistry.PYRA
        Event.SKEWB -> PuzzleRegistry.SKEWB
        Event.SQ1 -> PuzzleRegistry.SQ1
        Event.FTO -> PuzzleRegistry.FTO
    }

    private fun submit(e: Event): Future<String> = pool.submit<String> { registry(e).scrambler.generateScramble() }

    override fun warm(event: Event) { ahead.computeIfAbsent(event, ::submit) }

    override fun next(event: Event): String {
        val f = ahead.remove(event) ?: submit(event)
        val s = f.get()
        ahead[event] = submit(event)
        return s
    }

    private val search by lazy { cs.min2phase.Search() }

    override fun solve3x3(facelets: String): String? {
        if (facelets == "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB") return ""
        val s = synchronized(search) { search.solution(facelets, 21, 100_000, 0, 0) }
        return if (s.startsWith("Error")) null else s.trim().replace(Regex("\\s+"), " ")
    }

    override fun previewSvg(event: Event, scramble: String): String? = runCatching {
        registry(event).scrambler.drawScramble(scramble, null).toString()
    }.getOrNull()
}