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

Exclude directions exist in upper inner char on lower inner char #590

Merged
merged 3 commits into from
Mar 11, 2024
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, tuanchauict
* Copyright (c) 2023-2024, tuanchauict
*/

package mono.graphics.board
Expand Down Expand Up @@ -39,10 +39,14 @@ internal object CrossingResources {
private val CONNECTABLE_CHARS = "─│┌└┐┘┬┴├┤┼".extendChars().flatMap { it.toList() }.toSet()

// TODO: Extend the list with complex combination chars.
private val LEFT_IN_CHARS: Set<Char> = "─┌└┬┴├┼".extendChars().flatMap { it.toList() }.toSet()
private val RIGHT_IN_CHARS: Set<Char> = "─┐┘┬┴┤┼".extendChars().flatMap { it.toList() }.toSet()
private val TOP_IN_CHARS: Set<Char> = "│┌┐┬├┤┼".extendChars().flatMap { it.toList() }.toSet()
private val BOTTOM_IN_CHARS: Set<Char> = "│└┘┴├┤┼".extendChars().flatMap { it.toList() }.toSet()
private val LEFT_IN_CHARS: Set<Char> =
"─┌└┬┴├┼".extendChars().flatMap { it.toList() }.toSet()
private val RIGHT_IN_CHARS: Set<Char> =
"─┐┘┬┴┤┼".extendChars().flatMap { it.toList() }.toSet()
private val TOP_IN_CHARS: Set<Char> =
"│┌┐┬├┤┼".extendChars().flatMap { it.toList() }.toSet()
private val BOTTOM_IN_CHARS: Set<Char> =
"│└┘┴├┤┼".extendChars().flatMap { it.toList() }.toSet()

private val SINGLE_CONNECTOR_CHAR_MAP = sequenceOf(
"─│" to mapOf(
Expand Down Expand Up @@ -362,7 +366,7 @@ internal object CrossingResources {
private const val MASK_RIGHT = MASK_SINGLE_RIGHT or MASK_BOLD_RIGHT or MASK_DOUBLE_RIGHT
private const val MASK_TOP = MASK_SINGLE_TOP or MASK_BOLD_TOP or MASK_DOUBLE_TOP
private const val MASK_BOTTOM = MASK_SINGLE_BOTTOM or MASK_BOLD_BOTTOM or MASK_DOUBLE_BOTTOM
private const val MASK_CROSS = MASK_SINGLE_CROSS or MASK_BOLD_CROSS or MASK_DOUBLE_CROSS
internal const val MASK_CROSS = MASK_SINGLE_CROSS or MASK_BOLD_CROSS or MASK_DOUBLE_CROSS

private val CHAR_TO_MASK_MAP = mapOf(
'─' to MASK_SINGLE_HORIZONTAL,
Expand Down Expand Up @@ -501,70 +505,60 @@ internal object CrossingResources {
private val MASK_TO_CHAR_MAP =
CHAR_TO_MASK_MAP.entries.associate { (key, value) -> value to key }

private const val DEBUG = false

init {
if (Build.DEBUG) {
if (Build.DEBUG && DEBUG) {
for ((key, value) in MASK_TO_CHAR_MAP) {
console.log(value.toString(), maskToString(key))
}
}
}

fun getCrossingChar(
char1: Char,
surroundingLeft1: Char,
surroundingRight1: Char,
surroundingTop1: Char,
surroundingBottom1: Char,
char2: Char,
surroundingLeft2: Char,
surroundingRight2: Char,
surroundingTop2: Char,
surroundingBottom2: Char
upper: Char,
adjacentLeftUpper: Char,
adjacentRightUpper: Char,
adjacentTopUpper: Char,
adjacentBottomUpper: Char,
lower: Char,
adjacentLeftLower: Char,
adjacentRightLower: Char,
adjacentTopLower: Char,
adjacentBottomLower: Char
): Char? {
val mask1 = getCharMask(char1, MASK_CROSS)
val mask2 = getCharMask(char2, MASK_CROSS)
val maskUpper = getCharMask(upper, MASK_CROSS)
// Directions exist in the upper char exclude the direction in the lower char.
val maskLower = getCharMask(
lower,
createExcludeMask(maskUpper)
)

val maskLeft =
if (surroundingLeft1 in LEFT_IN_CHARS || surroundingLeft2 in LEFT_IN_CHARS) {
MASK_LEFT
} else {
0
}
if (adjacentLeftUpper.hasLeft || adjacentLeftLower.hasLeft) MASK_LEFT else 0
val maskRight =
if (surroundingRight1 in RIGHT_IN_CHARS || surroundingRight2 in RIGHT_IN_CHARS) {
MASK_RIGHT
} else {
0
}
if (adjacentRightUpper.hasRight || adjacentRightLower.hasRight) MASK_RIGHT else 0
val maskTop =
if (surroundingTop1 in TOP_IN_CHARS || surroundingTop2 in TOP_IN_CHARS) {
MASK_TOP
} else {
0
}
if (adjacentTopUpper.hasTop || adjacentTopLower.hasTop) MASK_TOP else 0
val maskBottom =
if (surroundingBottom1 in BOTTOM_IN_CHARS || surroundingBottom2 in BOTTOM_IN_CHARS) {
MASK_BOTTOM
} else {
0
}
if (adjacentBottomUpper.hasBottom || adjacentBottomLower.hasBottom) MASK_BOTTOM else 0

val innerMask = mask1 or mask2
val innerMask = maskUpper or maskLower
val outerMask = maskLeft or maskRight or maskTop or maskBottom
val mask = innerMask and outerMask

if (Build.DEBUG) {
console.log(
listOf(
"$char1:${maskToString(mask1)}",
"$char2:${maskToString(mask2)}",
"$upper:${maskToString(maskUpper)}",
"$lower:${maskToString(maskLower)}",
"-> ${maskToString(innerMask)}"
).toString(),
listOf(
"$surroundingLeft1:$surroundingLeft2:${maskToString(maskLeft)}",
"$surroundingRight1:$surroundingRight2:${maskToString(maskRight)}",
"$surroundingTop1:$surroundingTop2:${maskToString(maskTop)}",
"$surroundingBottom1:$surroundingBottom2:${maskToString(maskBottom)}",
"$adjacentLeftUpper:$adjacentLeftLower:${maskToString(maskLeft)}",
"$adjacentRightUpper:$adjacentRightLower:${maskToString(maskRight)}",
"$adjacentTopUpper:$adjacentTopLower:${maskToString(maskTop)}",
"$adjacentBottomUpper:$adjacentBottomLower:${maskToString(maskBottom)}",
"-> ${maskToString(outerMask)}"
).toString(),
maskToString(outerMask),
Expand All @@ -576,12 +570,23 @@ internal object CrossingResources {
return MASK_TO_CHAR_MAP[mask]
}

private fun getCharMask(char: Char, mask: Int): Int {
val charMask = CHAR_TO_MASK_MAP[char] ?: 0
internal fun getCharMask(char: Char, mask: Int): Int {
val charMask = CHAR_TO_MASK_MAP[standardize(char)] ?: 0
return charMask and mask
}

private fun standardize(char: Char): Char = STANDARDIZED_CHARS[char] ?: char

private fun maskToString(mask: Int): String = mask.toString(2).padStart(12, '0')
fun maskToString(mask: Int): String = mask.toString(2).padStart(12, '0')

/**
* Creates a mask that excludes bits from any direction existing in the given mask.
* For example, if mask is from `│` (`0000.0000.1100`), the result will be `0011.0011.0011`
*/
internal fun createExcludeMask(mask: Int): Int {
// and with MASK_CROSS to remove the overflow bits.
val allDirectionsMask =
((mask shl 8) or (mask shl 4) or mask or (mask shr 4) or (mask shr 8)) and MASK_CROSS
return MASK_CROSS xor allDirectionsMask
}
}
22 changes: 11 additions & 11 deletions libs/monoboard/src/main/kotlin/mono/graphics/board/MonoBoard.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, tuanchauict
* Copyright (c) 2023-2024, tuanchauict
*/

package mono.graphics.board
Expand Down Expand Up @@ -103,16 +103,16 @@ class MonoBoard(private val unitSize: Size = STANDARD_UNIT_SIZE) {
val top = charPoint.top
val currentPixel = get(left, top)
val crossingChar = CrossingResources.getCrossingChar(
char1 = charPoint.visualChar,
surroundingLeft1 = charPoint.leftChar,
surroundingRight1 = charPoint.rightChar,
surroundingTop1 = charPoint.topChar,
surroundingBottom1 = charPoint.bottomChar,
char2 = get(left, top).visualChar,
surroundingLeft2 = get(left - 1, top).directionChar,
surroundingRight2 = get(left + 1, top).directionChar,
surroundingTop2 = get(left, top - 1).directionChar,
surroundingBottom2 = get(left, top + 1).directionChar
upper = charPoint.visualChar,
adjacentLeftUpper = charPoint.leftChar,
adjacentRightUpper = charPoint.rightChar,
adjacentTopUpper = charPoint.topChar,
adjacentBottomUpper = charPoint.bottomChar,
lower = get(left, top).visualChar,
adjacentLeftLower = get(left - 1, top).directionChar,
adjacentRightLower = get(left + 1, top).directionChar,
adjacentTopLower = get(left, top - 1).directionChar,
adjacentBottomLower = get(left, top + 1).directionChar
)
if (crossingChar != null) {
currentPixel.set(crossingChar, crossingChar, highlight)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, tuanchauict
*/

package mono.graphics.board

import kotlin.test.Test
import kotlin.test.assertEquals
import mono.graphics.board.CrossingResources.createExcludeMask
import mono.graphics.board.CrossingResources.getCharMask
import mono.graphics.board.CrossingResources.maskToString

/**
* A test for [CrossingResources].
*/
class CrossingResourcesTest {
@Test
fun testExcludeMask() {
console.log(maskToString(getCharMask('║', CrossingResources.MASK_CROSS)))
console.log(maskToString(createExcludeMask(getCharMask('║', CrossingResources.MASK_CROSS))))
assertEquals(
0b001100110011,
createExcludeMask(getCharMask('│', CrossingResources.MASK_CROSS))
)
assertEquals(
0b001100110011,
createExcludeMask(getCharMask('┃', CrossingResources.MASK_CROSS))
)
assertEquals(
0b001100110011,
createExcludeMask(getCharMask('║', CrossingResources.MASK_CROSS))
)

assertEquals(
0b0110011001100,
createExcludeMask(getCharMask('─', CrossingResources.MASK_CROSS))
)
assertEquals(
0b0110011001100,
createExcludeMask(getCharMask('━', CrossingResources.MASK_CROSS))
)
assertEquals(
0b0110011001100,
createExcludeMask(getCharMask('═', CrossingResources.MASK_CROSS))
)
}
}
Loading