Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for splitting and chunking scrambles in PDF #881

Merged
merged 4 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ enum class EventData(val id: String, val description: String, val scrambler: Puz
constructor(scrambler: PuzzleData, legalFormats: Set<FormatData>) : 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ enum class PuzzleData(

private val SCRAMBLE_CACHERS = mutableMapOf<String, CoroutineScrambleCacher>()

val WCA_PUZZLES = values().associateBy { it.id }.toSortedMap()
val WCA_PUZZLES = entries.associateBy { it.id }.toSortedMap()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ object FontUtil {
return lineTokens.map { it.joinToStringWithPadding(chunkGlue, padding) }
}

fun splitAtPossibleBreaks(
tailrec fun splitAtPossibleBreaks(
chunksWithBreakFlags: List<Pair<String, Boolean>>,
currentPhraseAccu: List<String> = emptyList(),
accu: List<List<String>> = emptyList()
Expand Down Expand Up @@ -120,7 +120,7 @@ object FontUtil {
}
}

fun splitToMaxFontSizeLines(
tailrec fun splitToMaxFontSizeLines(
chunkSections: List<List<String>>,
boxHeight: Float,
boxWidth: Float,
Expand Down Expand Up @@ -166,7 +166,7 @@ object FontUtil {
return accu.toMutableList().apply { add(element) }
}

private fun splitChunksToLines(
private tailrec fun splitChunksToLines(
chunks: List<List<String>>,
lineRelWidth: Float,
chunkGlue: String,
Expand All @@ -189,7 +189,7 @@ object FontUtil {
}
}

private fun takeChunksThatFitOneLine(
private tailrec fun takeChunksThatFitOneLine(
chunkSections: List<List<String>>,
lineRelativeWidth: Float,
chunkGlue: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ enum class Gender(val wcaString: String) {
OTHER("o");

companion object : SingletonStringEncoder<Gender>("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 }}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ enum class RegistrationStatus(val wcaString: String) {
DELETED("deleted");

companion object : SingletonStringEncoder<RegistrationStatus>("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 }}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ enum class ResultType(val wcaString: String) {
AVERAGE("average");

companion object : SingletonStringEncoder<ResultType>("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 }}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}