diff --git a/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/EventData.kt b/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/EventData.kt index b28c7b610..fd5ae9a77 100644 --- a/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/EventData.kt +++ b/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/EventData.kt @@ -22,7 +22,7 @@ enum class EventData(val id: String, val description: String, val scrambler: Puz constructor(scrambler: PuzzleData, legalFormats: Set) : this(scrambler.id, scrambler.description, scrambler, legalFormats) companion object { - val WCA_EVENTS = values().associateBy { it.id }.toSortedMap() + val WCA_EVENTS = entries.associateBy { it.id }.toSortedMap() val ONE_HOUR_EVENTS = setOf(THREE_FM, THREE_MULTI_BLD) val ATTEMPT_BASED_EVENTS = setOf(THREE_FM, THREE_MULTI_BLD) diff --git a/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/FormatData.kt b/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/FormatData.kt index f74a55bd8..6ce313fa0 100644 --- a/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/FormatData.kt +++ b/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/FormatData.kt @@ -8,7 +8,7 @@ enum class FormatData(val key: String, val description: String, val tag: String, BEST_OF_1("1", "Best of 1", "Bo1", 1); companion object { - val WCA_FORMATS = values().associateBy { it.key }.toSortedMap() + val WCA_FORMATS = entries.associateBy { it.key }.toSortedMap() val BIG_AVERAGE_FORMATS = sortedSetOf(AVERAGE_OF_5, BEST_OF_3, BEST_OF_2, BEST_OF_1) val SMALL_AVERAGE_FORMATS = sortedSetOf(MEAN_OF_3, BEST_OF_2, BEST_OF_1) diff --git a/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/PuzzleData.kt b/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/PuzzleData.kt index 53d962d3b..3d7b0048e 100644 --- a/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/PuzzleData.kt +++ b/tnoodle-server/src/main/kotlin/org/worldcubeassociation/tnoodle/server/model/PuzzleData.kt @@ -62,6 +62,6 @@ enum class PuzzleData( private val SCRAMBLE_CACHERS = mutableMapOf() - val WCA_PUZZLES = values().associateBy { it.id }.toSortedMap() + val WCA_PUZZLES = entries.associateBy { it.id }.toSortedMap() } } diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/FontUtil.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/FontUtil.kt index f8f128335..4c9b21839 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/FontUtil.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/FontUtil.kt @@ -88,7 +88,7 @@ object FontUtil { return lineTokens.map { it.joinToStringWithPadding(chunkGlue, padding) } } - fun splitAtPossibleBreaks( + tailrec fun splitAtPossibleBreaks( chunksWithBreakFlags: List>, currentPhraseAccu: List = emptyList(), accu: List> = emptyList() @@ -120,7 +120,7 @@ object FontUtil { } } - fun splitToMaxFontSizeLines( + tailrec fun splitToMaxFontSizeLines( chunkSections: List>, boxHeight: Float, boxWidth: Float, @@ -166,7 +166,7 @@ object FontUtil { return accu.toMutableList().apply { add(element) } } - private fun splitChunksToLines( + private tailrec fun splitChunksToLines( chunks: List>, lineRelWidth: Float, chunkGlue: String, @@ -189,7 +189,7 @@ object FontUtil { } } - private fun takeChunksThatFitOneLine( + private tailrec fun takeChunksThatFitOneLine( chunkSections: List>, lineRelativeWidth: Float, chunkGlue: String, diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/ScrambleStringUtil.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/ScrambleStringUtil.kt index 6e95d113a..66f7fd33c 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/ScrambleStringUtil.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/util/ScrambleStringUtil.kt @@ -6,12 +6,12 @@ import kotlin.math.ceil object ScrambleStringUtil { const val MOVES_DELIMITER = " " - val NBSP_STRING = Typography.nbsp.toString() + const val NBSP_STRING = Typography.nbsp.toString() - val MIN_ONE_LINE_FONT_SIZE = 15f - val MAX_PHRASE_FONT_SIZE = 20f + const val MIN_ONE_LINE_FONT_SIZE = 15f + const val MAX_PHRASE_FONT_SIZE = 20f - fun padTurnsUniformly(scramble: String, padding: String = NBSP_STRING): String { + private fun padTurnsUniformly(scramble: String, padding: String = NBSP_STRING): String { val maxTurnLength = scramble.split("\\s+".toRegex()).maxOfOrNull { it.length } ?: 0 val lines = scramble.lines().dropLastWhile { it.isEmpty() } diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/ReadmeHandler.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/ReadmeHandler.kt index 4ab5df95e..10d879522 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/ReadmeHandler.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/ReadmeHandler.kt @@ -15,7 +15,7 @@ object ReadmeHandler : RouteHandler { val scramblesReadmeStream = ReadmeHandler.javaClass.getResourceAsStream("/wca/readme-scramble.md") val rawReadme = scramblesReadmeStream.bufferedReader().readText() - val scrambleFilteringInfo = PuzzleData.values() + val scrambleFilteringInfo = PuzzleData.entries .map { it.scrambler } .joinToString("\n") { // those 2 spaces at the end are no accident: http://meta.stackoverflow.com/questions/26011/should-the-markdown-renderer-treat-a-single-line-break-as-br diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/frontend/ApplicationDataHandler.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/frontend/ApplicationDataHandler.kt index d5ec531f6..50c903e46 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/frontend/ApplicationDataHandler.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/routing/frontend/ApplicationDataHandler.kt @@ -13,7 +13,7 @@ object ApplicationDataHandler : RouteHandler { override fun install(router: Route) { router.route("data") { get("events") { - val eventData = EventData.values().map(EventFrontendData.Companion::fromDataModel) + val eventData = EventData.entries.map(EventFrontendData.Companion::fromDataModel) call.respond(eventData) } diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/Gender.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/Gender.kt index df2d9839e..0aeb89d9b 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/Gender.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/Gender.kt @@ -9,10 +9,10 @@ enum class Gender(val wcaString: String) { OTHER("o"); companion object : SingletonStringEncoder("Gender") { - fun fromWCAString(wcaString: String) = values().find { it.wcaString == wcaString } + fun fromWCAString(wcaString: String) = entries.find { it.wcaString == wcaString } override fun encodeInstance(instance: Gender) = instance.wcaString override fun makeInstance(deserialized: String) = fromWCAString(deserialized) - ?: BadWcifParameterException.error("Unknown WCIF spec Gender: '$deserialized'. Valid types: ${values().map { it.wcaString }}") + ?: BadWcifParameterException.error("Unknown WCIF spec Gender: '$deserialized'. Valid types: ${entries.map { it.wcaString }}") } } diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/RegistrationStatus.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/RegistrationStatus.kt index 80a3f3a0b..c977c9851 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/RegistrationStatus.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/RegistrationStatus.kt @@ -9,10 +9,10 @@ enum class RegistrationStatus(val wcaString: String) { DELETED("deleted"); companion object : SingletonStringEncoder("RegistrationStatus") { - fun fromWCAString(wcaString: String) = values().find { it.wcaString == wcaString } + fun fromWCAString(wcaString: String) = entries.find { it.wcaString == wcaString } override fun encodeInstance(instance: RegistrationStatus) = instance.wcaString override fun makeInstance(deserialized: String) = fromWCAString(deserialized) - ?: BadWcifParameterException.error("Unknown WCIF spec RegistrationStatus: '$deserialized'. Valid types: ${values().map { it.wcaString }}") + ?: BadWcifParameterException.error("Unknown WCIF spec RegistrationStatus: '$deserialized'. Valid types: ${entries.map { it.wcaString }}") } } diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/ResultType.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/ResultType.kt index b6b32cb2e..9e50e95d3 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/ResultType.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/ResultType.kt @@ -8,10 +8,10 @@ enum class ResultType(val wcaString: String) { AVERAGE("average"); companion object : SingletonStringEncoder("ResultType") { - fun fromWCAString(wcaString: String) = values().find { it.wcaString == wcaString } + fun fromWCAString(wcaString: String) = entries.find { it.wcaString == wcaString } override fun encodeInstance(instance: ResultType) = instance.wcaString override fun makeInstance(deserialized: String) = fromWCAString(deserialized) - ?: BadWcifParameterException.error("Unknown WCIF spec ResultType: '$deserialized'. Valid types: ${values().map { it.wcaString }}") + ?: BadWcifParameterException.error("Unknown WCIF spec ResultType: '$deserialized'. Valid types: ${entries.map { it.wcaString }}") } } diff --git a/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/PdfRenderingTest.kt b/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/PdfRenderingTest.kt index b56a5f964..e98705f68 100644 --- a/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/PdfRenderingTest.kt +++ b/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/PdfRenderingTest.kt @@ -54,7 +54,7 @@ class PdfRenderingTest { @Test fun `test that 3+2 scrambles get displayed on one page`() { - for (event in EventData.values()) { + for (event in EventData.entries) { println("Rendering 3+2 layout for ${event.id}") repeat(SCRAMBLE_REPETITIONS) { @@ -66,7 +66,7 @@ class PdfRenderingTest { @Test fun `test that 5+2 scrambles get displayed on one page`() { - for (event in EventData.values()) { + for (event in EventData.entries) { println("Rendering 5+2 layout for ${event.id}") repeat(SCRAMBLE_REPETITIONS) { @@ -78,7 +78,7 @@ class PdfRenderingTest { @Test fun `test that 7+0 scrambles get displayed on one page`() { - for (event in EventData.values()) { + for (event in EventData.entries) { println("Rendering 7+0 layout for ${event.id}") repeat(SCRAMBLE_REPETITIONS) { diff --git a/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/TokenizerTest.kt b/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/TokenizerTest.kt new file mode 100644 index 000000000..894b74509 --- /dev/null +++ b/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/TokenizerTest.kt @@ -0,0 +1,129 @@ +package org.worldcubeassociation.tnoodle.server.webscrambles + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.worldcubeassociation.tnoodle.server.model.EventData +import org.worldcubeassociation.tnoodle.server.webscrambles.pdf.util.FontUtil +import org.worldcubeassociation.tnoodle.server.webscrambles.pdf.util.ScrambleStringUtil + +class TokenizerTest { + @Test + fun `test that tokenizing scramble strings does not lose information`() { + for (event in EventData.entries) { + println("Generating random scrambles for ${event.id}") + + event.scrambler.generateEfficientScrambles(SCRAMBLE_REPETITIONS) { + val tokenizedScramble = ScrambleStringUtil.splitToTokens(it) + val gluedTogetherScramble = tokenizedScramble.joinToString(ScrambleStringUtil.MOVES_DELIMITER) { (str, _) -> str.trim() } + + assertScramblesEqual(event, it, gluedTogetherScramble) + } + } + } + + @Test + fun `test that splitting scramble strings does not lose information`() { + for (event in EventData.entries) { + println("Generating random scrambles for ${event.id}") + + event.scrambler.generateEfficientScrambles(SCRAMBLE_REPETITIONS) { + val tokenizedScramble = ScrambleStringUtil.split(it) + val gluedTogetherScramble = tokenizedScramble.joinToString(ScrambleStringUtil.MOVES_DELIMITER) + + assertScramblesEqual(event, it, gluedTogetherScramble) + } + } + } + + @Test + fun `test that splitting to lines does not lose information`() { + for (event in EventData.entries) { + println("Generating random scrambles for ${event.id}") + + event.scrambler.generateEfficientScrambles(SCRAMBLE_REPETITIONS) { + val tokenizedScramble = ScrambleStringUtil.splitToTokens(it) + val lineSplitScramble = FontUtil.splitAtPossibleBreaks(tokenizedScramble) + + val gluedTogetherScramble = lineSplitScramble.joinToString(ScrambleStringUtil.MOVES_DELIMITER) { line -> + line.joinToString(ScrambleStringUtil.MOVES_DELIMITER) { str -> str.trim() } + } + + assertScramblesEqual(event, it, gluedTogetherScramble) + } + } + } + + @Test + fun `test that splitting to fixed size does not lose information`() { + for (event in EventData.entries) { + println("Generating random scrambles for ${event.id}") + + event.scrambler.generateEfficientScrambles(SCRAMBLE_REPETITIONS) { + val tokenizedScramble = ScrambleStringUtil.splitToTokens(it) + val lineSplitScramble = FontUtil.splitAtPossibleBreaks(tokenizedScramble) + + for (fontSize in listOf(3f, 12f, 120f)) { + for (lineWidth in listOf(12f, 120f, 1200f)) { + for (unitToInches in listOf(.2f, 1f, 2f, 2000f)) { + val lines = FontUtil.splitToFixedSizeLines( + lineSplitScramble, + fontSize, + lineWidth, + unitToInches, + ScrambleStringUtil.MOVES_DELIMITER + ) + + val gluedTogetherScramble = lines.joinToString(ScrambleStringUtil.MOVES_DELIMITER) { str -> str.trim() } + .replace(ScrambleStringUtil.NBSP_STRING, "") + + assertScramblesEqual(event, it, gluedTogetherScramble) + } + } + } + } + } + } + + @Test + fun `test that splitting to max size does not lose information`() { + for (event in EventData.entries) { + println("Generating random scrambles for ${event.id}") + + event.scrambler.generateEfficientScrambles(SCRAMBLE_REPETITIONS) { + val tokenizedScramble = ScrambleStringUtil.splitToTokens(it) + val lineSplitScramble = FontUtil.splitAtPossibleBreaks(tokenizedScramble) + + for (boxHeight in listOf(12f, 120f, 1200f)) { + for (boxWidth in listOf(12f, 120f, 1200f)) { + for (leadingFactor in listOf(.9f, 1.2f, 2f)) { + val lines = FontUtil.splitToMaxFontSizeLines( + lineSplitScramble, + boxHeight, + boxWidth, + leadingFactor, + ScrambleStringUtil.MOVES_DELIMITER + ) + + val gluedTogetherScramble = lines.joinToString(ScrambleStringUtil.MOVES_DELIMITER) { str -> str.trim() } + .replace(ScrambleStringUtil.NBSP_STRING, "") + + assertScramblesEqual(event, it, gluedTogetherScramble) + } + } + } + } + } + } + + companion object { + const val SCRAMBLE_REPETITIONS = 20 + + fun assertScramblesEqual(event: EventData, original: String, reconstructed: String) { + // With Megaminx, it is a bit bothersome to reconstruct where the original newlines were. + // Since they are cosmetical anyways, we just ignore them even in the original scramble. + val originalScramble = if (event.id == "minx") original.replace("\n", " ") else original + + Assertions.assertEquals(originalScramble, reconstructed) + } + } +}