commonMain.mahjongutils.shanten.Shanten.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mahjong-utils-jvm Show documentation
Show all versions of mahjong-utils-jvm Show documentation
Mahjong Utils (for Japanese Riichi Mahjong)
package mahjongutils.shanten
import mahjongutils.CalcContext
import mahjongutils.models.Furo
import mahjongutils.models.Tile
import mahjongutils.models.countAsCodeArray
import mahjongutils.models.hand.CommonHandPattern
import mahjongutils.models.hand.Hand
import mahjongutils.shanten.helpers.fillNum
import mahjongutils.shanten.helpers.normalizeTiles
import kotlin.jvm.JvmOverloads
import kotlin.math.min
/**
* 向听分析参数(内部计算使用)
* @param tiles 门前的牌
* @param furo 副露(对向听分析本身无用,但若需要将结果用于和了分析则需要传入)
* @param calcAdvanceNum 是否计算进张数
* @param calcGoodShapeAdvance 是否计算好型进张
* @param bestShantenOnly 仅计算最优向听数的打法(不计算退向打法)
* @param allowAnkan 是否允许暗杠
* @param calcImprovement 是否计算改良
* @return 向听分析结果
*/
internal data class InternalShantenArgs(
val tiles: List,
val furo: List = emptyList(),
val calcAdvanceNum: Boolean = true,
val calcGoodShapeAdvance: Boolean = true,
val bestShantenOnly: Boolean = false,
val allowAnkan: Boolean = true,
val calcImprovement: Boolean = true,
)
/**
* 向听分析
* @param tiles 门前的牌
* @param furo 副露(对向听分析本身无用,但若需要将结果用于和了分析则需要传入)
* @param bestShantenOnly 仅计算最优向听数的打法(不计算退向打法)
* @return 向听分析结果
*/
@JvmOverloads
fun shanten(
tiles: List,
furo: List = emptyList(),
bestShantenOnly: Boolean = false,
): UnionShantenResult {
return shanten(CommonShantenArgs(tiles = tiles, furo = furo, bestShantenOnly = bestShantenOnly))
}
/**
* 向听分析
* @param args 向听分析参数
* @return 向听分析结果
*/
fun shanten(
args: CommonShantenArgs
): UnionShantenResult {
args.throwOnValidationError()
val internalShantenArgs = InternalShantenArgs(
tiles = args.tiles,
furo = args.furo,
bestShantenOnly = args.bestShantenOnly
)
val context = CalcContext()
return context.shanten(internalShantenArgs)
}
internal fun CalcContext.shanten(
args: InternalShantenArgs
): UnionShantenResult = memo(Pair("shanten", args)) {
with(args) {
val tiles = normalizeTiles(tiles)
val withGot = tiles.size % 3 == 2
val k = tiles.size / 3
if (k != 4) {
val regular = regularShanten(
copy(tiles = tiles)
)
return UnionShantenResult(
hand = regular.hand, shantenInfo = regular.shantenInfo,
regular = regular
)
}
val regular = regularShanten(
copy(tiles = tiles, calcAdvanceNum = false)
)
val chitoi = chitoiShanten(
InternalShantenArgs(tiles = tiles, calcAdvanceNum = false, bestShantenOnly = bestShantenOnly)
)
val kokushi = kokushiShanten(
InternalShantenArgs(tiles = tiles, calcAdvanceNum = false, bestShantenOnly = bestShantenOnly)
)
val shantenNum = min(
min(
regular.shantenInfo.shantenNum,
chitoi.shantenInfo.shantenNum
),
kokushi.shantenInfo.shantenNum
)
val patterns = ArrayList()
var shantenInfo = if (!withGot) {
val advance = HashSet()
val goodShapeAdvance = HashSet()
mergeIntoWithoutGot(shantenNum, advance, goodShapeAdvance, patterns, regular)
mergeIntoWithoutGot(shantenNum, advance, goodShapeAdvance, patterns, chitoi)
mergeIntoWithoutGot(shantenNum, advance, goodShapeAdvance, patterns, kokushi)
ShantenWithoutGot(
shantenNum, advance,
goodShapeAdvance = if (shantenNum == 1) goodShapeAdvance else null,
improvement = (regular.shantenInfo.asWithoutGot).improvement
?: (if (shantenNum == 0) emptyMap() else null),
goodShapeImprovement = (regular.shantenInfo.asWithoutGot).goodShapeImprovement
?: (if (shantenNum == 0) emptyMap() else null)
)
} else {
val discardToAdvance = HashMap()
mergeIntoWithGot(shantenNum, discardToAdvance, patterns, regular, bestShantenOnly)
mergeIntoWithGot(shantenNum, discardToAdvance, patterns, chitoi, bestShantenOnly)
mergeIntoWithGot(shantenNum, discardToAdvance, patterns, kokushi, bestShantenOnly)
ShantenWithGot(
shantenNum, discardToAdvance,
ankanToAdvance = (regular.shantenInfo.asWithGot).ankanToAdvance
)
}
if (calcAdvanceNum) {
val tilesCount = (tiles + furo.flatMap { it.asMentsu().tiles }).countAsCodeArray()
shantenInfo = shantenInfo.fillNum(tilesCount)
}
val hand = Hand(tiles = tiles, furo = furo, patterns = patterns)
return UnionShantenResult(
hand = hand, shantenInfo = shantenInfo,
regular = regular, chitoi = chitoi, kokushi = kokushi
)
}
}
private fun mergeIntoWithoutGot(
targetShantenNum: Int,
advance: MutableSet,
goodShapeAdvance: MutableSet,
patterns: MutableCollection,
result: ShantenResult
) {
if (result.shantenInfo.shantenNum == targetShantenNum) {
val shantenInfo = result.shantenInfo.asWithoutGot
advance += shantenInfo.advance
patterns += result.hand.patterns
if (shantenInfo.goodShapeAdvance != null) {
goodShapeAdvance += shantenInfo.goodShapeAdvance
}
}
}
private fun mergeIntoWithGot(
targetShantenNum: Int,
discardToAdvance: MutableMap,
patterns: MutableCollection,
result: ShantenResult,
bestShantenOnly: Boolean
) {
val shantenInfo = result.shantenInfo.asWithGot
for ((discard, shantenAfterDiscard) in shantenInfo.discardToAdvance) {
if (!bestShantenOnly || shantenAfterDiscard.shantenNum == targetShantenNum) {
val oldShantenAfterDiscard = discardToAdvance[discard]
if (oldShantenAfterDiscard == null || oldShantenAfterDiscard.shantenNum > shantenAfterDiscard.shantenNum) {
discardToAdvance[discard] = shantenAfterDiscard
} else if (oldShantenAfterDiscard.shantenNum == shantenAfterDiscard.shantenNum) {
discardToAdvance[discard] = oldShantenAfterDiscard.copy(
advance = oldShantenAfterDiscard.advance + shantenAfterDiscard.advance,
goodShapeAdvance = shantenAfterDiscard.goodShapeAdvance?.let {
oldShantenAfterDiscard.goodShapeAdvance?.plus(it)
}
)
}
}
}
if (targetShantenNum == shantenInfo.shantenNum) {
patterns += result.hand.patterns
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy