diff --git a/mahjong-utils-entry/build.gradle.kts b/mahjong-utils-entry/build.gradle.kts index ce0fc29..d88fb9d 100644 --- a/mahjong-utils-entry/build.gradle.kts +++ b/mahjong-utils-entry/build.gradle.kts @@ -87,16 +87,16 @@ kotlin { } if (enableWasm) { val wasmJsMain by getting { - dependsOn(nonJsMain) + dependsOn(wasmMain) } val wasmJsTest by getting { - dependsOn(nonJsTest) + dependsOn(wasmTest) } val wasmWasiMain by getting { - dependsOn(nonJsMain) + dependsOn(wasmMain) } val wasmWasiTest by getting { - dependsOn(nonJsTest) + dependsOn(wasmTest) } } } diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/hora/helpers/CalcHu.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/hora/helpers/CalcHu.kt index 186c6c2..3407f52 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/hora/helpers/CalcHu.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/hora/helpers/CalcHu.kt @@ -25,38 +25,36 @@ internal fun calcHuForRegular(regularPattern: RegularHoraHandPattern, hasRenpuuJ var ans = 20 // 单骑、边张、坎张听牌 - if (agariTatsu == null || agariTatsu is Penchan || agariTatsu is Kanchan) { + if (agariTatsu == null || agariTatsu.type == TatsuType.Penchan || agariTatsu.type == TatsuType.Kanchan) { ans += 2 } // 明刻、杠 furo.forEach { fr -> - if (fr is Pon) { + if (fr.type == FuroType.Pon) { if (fr.tile.isYaochu) { ans += 4 } else { ans += 2 } - } else if (fr is Kan) { - if (!fr.ankan) { - if (fr.tile.isYaochu) { - ans += 16 - } else { - ans += 8 - } + } else if (fr.type == FuroType.Ankan) { + if (fr.tile.isYaochu) { + ans += 32 } else { - if (fr.tile.isYaochu) { - ans += 32 - } else { - ans += 16 - } + ans += 16 + } + } else if (fr.type == FuroType.Kan) { + if (fr.tile.isYaochu) { + ans += 16 + } else { + ans += 8 } } } // 暗刻(不含暗杠) menzenMentsu.forEach { mt -> - if (mt is Kotsu) { + if (mt.type == MentsuType.Kotsu) { if (mt.tile.isYaochu) { ans += 8 } else { @@ -66,7 +64,7 @@ internal fun calcHuForRegular(regularPattern: RegularHoraHandPattern, hasRenpuuJ } // 对碰荣和时该刻子计为明刻 - if (agariTatsu is Toitsu && !tsumo) { + if (agariTatsu?.type == TatsuType.Toitsu && !tsumo) { ans -= 2 } diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Furo.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Furo.kt index 22bbd9e..199078d 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Furo.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Furo.kt @@ -1,23 +1,53 @@ -@file:OptIn(ExperimentalSerializationApi::class) - package mahjongutils.models -import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.jvm.JvmInline + +enum class FuroType { + Chi, Pon, Kan, Ankan +} + +@JvmInline +@Serializable(FuroSerializer::class) +value class Furo private constructor(private val value: Int) { + constructor(type: FuroType, tile: Tile) : this(type.ordinal * 100 + tile.code) + + val type: FuroType + get() = FuroType.entries[value / 100] + + val tile: Tile + get() = Tile[value % 100] -/** - * 副露 - */ -@Serializable -sealed interface Furo { /** * 获取副露的面子 */ - fun asMentsu(): Mentsu + fun asMentsu(): Mentsu { + return when (type) { + FuroType.Chi -> Shuntsu(tile) + FuroType.Pon, FuroType.Kan, FuroType.Ankan -> Kotsu(tile) + } + } val tiles: List + get() = when (type) { + FuroType.Chi -> listOf(tile, tile.advance(1), tile.advance(2)) + FuroType.Pon -> listOf(tile, tile, tile) + FuroType.Kan, FuroType.Ankan -> listOf(tile, tile, tile, tile) + } + + override fun toString(): String { + return when (type) { + FuroType.Chi -> "${tile.num}${tile.realNum + 1}${tile.realNum + 2}${tile.type.name.lowercase()}" + FuroType.Pon -> "${tile.num}${tile.num}${tile.num}${tile.type.name.lowercase()}" + FuroType.Kan -> "${tile.num}${tile.num}${tile.num}${tile.num}${tile.type.name.lowercase()}" + FuroType.Ankan -> "0${tile.num}${tile.num}0${tile.type.name.lowercase()}" + } + } companion object { /** @@ -44,7 +74,11 @@ sealed interface Furo { } } else if (tiles.size == 4) { if (tiles[0] == tiles[1] && tiles[1] == tiles[2] && tiles[2] == tiles[3]) { - return Kan(tiles[0], ankan) + if (!ankan) { + return Kan(tiles[0]) + } else { + return Ankan(tiles[0]) + } } } @@ -72,10 +106,6 @@ sealed interface Furo { } } -fun Furo(vararg tiles: Tile, ankan: Boolean = false): Furo { - return Furo.parse(tiles.toList(), ankan) -} - fun Furo(tiles: List, ankan: Boolean = false): Furo { return Furo.parse(tiles, ankan) } @@ -84,63 +114,20 @@ fun Furo(text: String, ankan: Boolean = false): Furo { return Furo.parse(text, ankan) } -/** - * 吃 - */ -@Serializable -@SerialName("Chi") -data class Chi( - /** - * 吃成的顺子的第一张牌(如789m,tile应为7m) - */ - val tile: Tile -) : Furo { - override fun asMentsu(): Shuntsu { - return Shuntsu(tile) - } +fun Chi(tile: Tile) = Furo(FuroType.Chi, tile) +fun Pon(tile: Tile) = Furo(FuroType.Pon, tile) +fun Kan(tile: Tile) = Furo(FuroType.Kan, tile) +fun Ankan(tile: Tile) = Furo(FuroType.Ankan, tile) - override val tiles: List - get() = listOf(tile, tile.advance(1), tile.advance(2)) -} +object FuroSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("Furo", PrimitiveKind.STRING) -/** - * 碰 - */ -@Serializable -@SerialName("Pon") -data class Pon( - /** - * 碰成的刻子的牌(如777s,tile应为7s) - */ - val tile: Tile -) : Furo { - override fun asMentsu(): Kotsu { - return Kotsu(tile) + override fun serialize(encoder: Encoder, value: Furo) { + encoder.encodeString(value.toString()) } - override val tiles: List - get() = listOf(tile, tile, tile) -} - -/** - * 杠 - */ -@Serializable -@SerialName("Kan") -data class Kan( - /** - * 杠成的刻子的牌(如7777s,tile应为7s) - */ - val tile: Tile, - /** - * 是否为暗杠 - */ - @EncodeDefault val ankan: Boolean = false -) : Furo { - override fun asMentsu(): Kotsu { - return Kotsu(tile) + override fun deserialize(decoder: Decoder): Furo { + val text = decoder.decodeString() + return Furo(text) } - - override val tiles: List - get() = listOf(tile, tile, tile, tile) } diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Mentsu.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Mentsu.kt index 1f17d2a..d75430f 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Mentsu.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Mentsu.kt @@ -1,27 +1,78 @@ package mahjongutils.models import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.jvm.JvmInline + +enum class MentsuType { + Shuntsu, Kotsu +} /** * 面子 */ +@JvmInline @Serializable(with = MentsuSerializer::class) -sealed interface Mentsu { +value class Mentsu private constructor(private val value: Int) { + val type: MentsuType + get() = MentsuType.entries[value / 100] + + val tile: Tile + get() = Tile[value % 100] + + constructor(type: MentsuType, tile: Tile) : this(type.ordinal * 100 + tile.code) + /** * 所含的牌 */ val tiles: Iterable + get() = when (type) { + MentsuType.Shuntsu -> listOf(tile, tile.advance(1), tile.advance(2)) + MentsuType.Kotsu -> listOf(tile, tile, tile) + } /** * 舍牌后形成的搭子 */ - fun afterDiscard(discard: Tile): Tatsu + fun afterDiscard(discard: Tile): Tatsu { + return when (type) { + MentsuType.Shuntsu -> { + if (discard == tile) { + if (discard.num == 7) + return Penchan(discard.advance(1)) + else + return Ryanmen(discard.advance(1)) + } else if (discard == tile.advance(1)) + return Kanchan(tile) + else if (discard == tile.advance(2)) { + if (tile.num == 1) + return Penchan(tile) + else + return Ryanmen(tile) + } + throw IllegalArgumentException("invalid discard: $discard") + } + + MentsuType.Kotsu -> { + if (discard == tile) { + return Toitsu(discard) + } + throw IllegalArgumentException("invalid discard: $discard") + + } + } + } + + override fun toString(): String { + return when (type) { + MentsuType.Shuntsu -> "${tile.num}${tile.realNum + 1}${tile.realNum + 2}${tile.type.name.lowercase()}" + MentsuType.Kotsu -> "${tile.num}${tile.num}${tile.num}${tile.type.name.lowercase()}" + } + } companion object { /** @@ -61,10 +112,6 @@ sealed interface Mentsu { } } -fun Mentsu(vararg tiles: Tile): Mentsu { - return Mentsu.parse(tiles.toList()) -} - fun Mentsu(tiles: List): Mentsu { return Mentsu.parse(tiles) } @@ -73,72 +120,9 @@ fun Mentsu(text: String): Mentsu { return Mentsu.parse(text) } -/** - * 刻子 - */ -@Serializable -@SerialName("Kotsu") -data class Kotsu( - /** - * 顺子的第一张牌(如789m,tile应为7m) - */ - val tile: Tile -) : Mentsu { - override val tiles: Iterable - get() = listOf(tile, tile, tile) - - override fun afterDiscard(discard: Tile): Tatsu { - if (discard == tile) { - return Toitsu(discard) - } - throw IllegalArgumentException("invalid discard: $discard") - } - - override fun toString(): String { - return "${tile.num}${tile.num}${tile.num}${tile.type.name.lowercase()}" - } -} - -/** - * 顺子 - */ -@Serializable -@SerialName("Shuntsu") -data class Shuntsu( - /** - * 刻子的牌(如777s,tile应为7s) - */ - val tile: Tile -) : Mentsu { - init { - require(tile.num in 1..7) - require(tile.type != TileType.Z) - } +fun Shuntsu(tile: Tile) = Mentsu(MentsuType.Shuntsu, tile) - override val tiles: Iterable - get() = listOf(tile, tile.advance(1), tile.advance(2)) - - override fun afterDiscard(discard: Tile): Tatsu { - if (discard == tile) { - if (discard.num == 7) - return Penchan(discard.advance(1)) - else - return Ryanmen(discard.advance(1)) - } else if (discard == tile.advance(1)) - return Kanchan(tile) - else if (discard == tile.advance(2)) { - if (tile.num == 1) - return Penchan(tile) - else - return Ryanmen(tile) - } - throw IllegalArgumentException("invalid discard: $discard") - } - - override fun toString(): String { - return "${tile.num}${tile.num + 1}${tile.num + 2}${tile.type.name.lowercase()}" - } -} +fun Kotsu(tile: Tile) = Mentsu(MentsuType.Kotsu, tile) internal class MentsuSerializer : KSerializer { override val descriptor = PrimitiveSerialDescriptor("Mentsu", PrimitiveKind.STRING) diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tatsu.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tatsu.kt index 54b6c9c..03e8488 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tatsu.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tatsu.kt @@ -1,37 +1,102 @@ package mahjongutils.models import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.jvm.JvmInline + +enum class TatsuType { + Ryanmen, Kanchan, Penchan, Toitsu +} /** * 搭子 */ +@JvmInline @Serializable(with = TatsuSerializer::class) -sealed interface Tatsu { +value class Tatsu private constructor(private val value: Int) { + val type: TatsuType + get() = TatsuType.entries[value / 100] + + constructor(type: TatsuType, first: Tile) : this(type.ordinal * 100 + first.code) + /** * 第一张牌 */ val first: Tile + get() = Tile[value % 100] /** * 第二张牌 */ val second: Tile + get() = when (type) { + TatsuType.Ryanmen -> first.advance(1) + TatsuType.Kanchan -> first.advance(2) + TatsuType.Penchan -> first.advance(1) + TatsuType.Toitsu -> first + } /** * 进张 */ val waiting: Set + get() = when (type) { + TatsuType.Ryanmen -> setOf(first.advance(-1), first.advance(2)) + TatsuType.Kanchan -> setOf(first.advance(1)) + TatsuType.Penchan -> if (first.num == 1) + setOf(first.advance(2)) + else + setOf(first.advance(-1)) + + TatsuType.Toitsu -> setOf(first) + } /** * 进张后形成的面子 */ - fun withWaiting(tile: Tile): Mentsu + fun withWaiting(tile: Tile): Mentsu { + when (type) { + TatsuType.Ryanmen -> { + if (tile == first.advance(-1)) + return Shuntsu(tile) + else if (tile == second.advance(1)) + return Shuntsu(first) + else + throw IllegalArgumentException("tile $tile is not waiting") + } + + TatsuType.Kanchan -> { + if (tile == first.advance(1)) + return Shuntsu(first) + else + throw IllegalArgumentException("tile $tile is not waiting") + } + + TatsuType.Penchan -> { + if (first.num == 1 && tile == first.advance(2)) + return Shuntsu(first) + if (first.num == 8 && tile == first.advance(-1)) + return Shuntsu(tile) + else + throw IllegalArgumentException("tile $tile is not waiting") + } + + TatsuType.Toitsu -> { + if (tile == first) + return Kotsu(first) + else + throw IllegalArgumentException("tile $tile is not waiting") + } + } + } + + override fun toString(): String { + return "${first.num}${second.num}${first.type.name.lowercase()}" + } companion object { /** @@ -94,123 +159,10 @@ fun Tatsu(text: String): Tatsu { return Tatsu.parse(text) } -/** - * 两面 - */ -@Serializable -@SerialName("Ryanmen") -data class Ryanmen(override val first: Tile) : Tatsu { - init { - require(first.num in 2..7) - require(first.type != TileType.Z) - } - - override val second: Tile - get() = first.advance(1) - - override val waiting: Set - get() = setOf(first.advance(-1), first.advance(2)) - - override fun withWaiting(tile: Tile): Shuntsu { - if (tile == first.advance(-1)) - return Shuntsu(tile) - else if (tile == second.advance(1)) - return Shuntsu(first) - else - throw IllegalArgumentException("tile $tile is not waiting") - } - - override fun toString(): String { - return "${first.num}${second.num}${first.type.name.lowercase()}" - } -} - -/** - * 坎张 - */ -@Serializable -@SerialName("Kanchan") -data class Kanchan(override val first: Tile) : Tatsu { - init { - require(first.num in 1..7) - require(first.type != TileType.Z) - } - - override val second: Tile - get() = first.advance(2) - - override val waiting: Set - get() = setOf(first.advance(1)) - - override fun withWaiting(tile: Tile): Shuntsu { - if (tile == first.advance(1)) - return Shuntsu(first) - else - throw IllegalArgumentException("tile $tile is not waiting") - } - - override fun toString(): String { - return "${first.num}${second.num}${first.type.name.lowercase()}" - } -} - -/** - * 边张 - */ -@Serializable -@SerialName("Penchan") -data class Penchan(override val first: Tile) : Tatsu { - init { - require(first.num == 1 || first.num == 8) - require(first.type != TileType.Z) - } - - override val second: Tile - get() = first.advance(1) - - override val waiting: Set - get() = if (first.num == 1) - setOf(first.advance(2)) - else - setOf(first.advance(-1)) - - override fun withWaiting(tile: Tile): Shuntsu { - if (first.num == 1 && tile == first.advance(2)) - return Shuntsu(first) - if (first.num == 8 && tile == first.advance(-1)) - return Shuntsu(tile) - else - throw IllegalArgumentException("tile $tile is not waiting") - } - - override fun toString(): String { - return "${first.num}${second.num}${first.type.name.lowercase()}" - } -} - -/** - * 对子 - */ -@Serializable -@SerialName("Toitsu") -data class Toitsu(override val first: Tile) : Tatsu { - override val second: Tile - get() = first - - override val waiting: Set - get() = setOf(first) - - override fun withWaiting(tile: Tile): Kotsu { - if (tile == first) - return Kotsu(first) - else - throw IllegalArgumentException("tile $tile is not waiting") - } - - override fun toString(): String { - return "${first.num}${second.num}${first.type.name.lowercase()}" - } -} +fun Ryanmen(tile: Tile) = Tatsu(TatsuType.Ryanmen, tile) +fun Kanchan(tile: Tile) = Tatsu(TatsuType.Kanchan, tile) +fun Penchan(tile: Tile) = Tatsu(TatsuType.Penchan, tile) +fun Toitsu(tile: Tile) = Tatsu(TatsuType.Toitsu, tile) internal class TatsuSerializer : KSerializer { override val descriptor = PrimitiveSerialDescriptor("Tatsu", PrimitiveKind.STRING) diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tile.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tile.kt index 7b81ea5..e348683 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tile.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/Tile.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.jvm.JvmInline /** * 麻将牌的种类(万、筒、索、字) @@ -47,22 +48,25 @@ enum class TileType { /** * 麻将牌 */ +@JvmInline @Serializable(with = TileSerializer::class) -data class Tile private constructor( +value class Tile private constructor( + val code: Int +) : Comparable { + /** * 种类 */ - val type: TileType, + val type: TileType + get() = TileType.valueOf(code / 10) + /** * 数字 */ val num: Int -) : Comparable { - /** - * 编号 - */ - val code: Int - get() = type.ordinal * 10 + num + get() = code % 10 + + constructor(type: TileType, num: Int) : this(type.ordinal * 10 + num) /** * 真正数字。当num为0时(该牌为红宝牌),realNum为5。其余情况下与num相等。 @@ -148,8 +152,12 @@ data class Tile private constructor( * 根据编号获取牌 */ operator fun get(code: Int): Tile { - if (code !in pool.indices || code == 30) { - throw IllegalArgumentException("invalid code: $code") + return getOrNull(code) ?: throw IllegalArgumentException("invalid code: $code") + } + + fun getOrNull(code: Int): Tile? { + if (code !in pool.indices) { + return null } return pool[code]!! } @@ -161,12 +169,20 @@ data class Tile private constructor( return get(type.ordinal * 10 + num) } + fun getOrNull(type: TileType, num: Int): Tile? { + return getOrNull(type.ordinal * 10 + num) + } + /** * 根据文本获取牌 */ operator fun get(text: String): Tile { + return getOrNull(text) ?: throw IllegalArgumentException("invalid tile text: $text") + } + + fun getOrNull(text: String): Tile? { if (text.length != 2) { - throw IllegalArgumentException("invalid tile text: $text") + return null } val type = when (text[1].lowercaseChar()) { @@ -174,12 +190,12 @@ data class Tile private constructor( 'p' -> TileType.P 's' -> TileType.S 'z' -> TileType.Z - else -> throw IllegalArgumentException("invalid tile text: $text") + else -> return null } - val num = text[0].digitToIntOrNull() ?: throw IllegalArgumentException("invalid tile text: $text") + val num = text[0].digitToIntOrNull() ?: return null - return get(type, num) + return getOrNull(type, num) } /** diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/HandPattern.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/HandPattern.kt index 2c2dc13..5ed8522 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/HandPattern.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/HandPattern.kt @@ -75,9 +75,9 @@ interface IRegularHandPattern : HandPattern { /** * 暗刻 */ - val anko: List - get() = menzenMentsu.filterIsInstance() + - furo.filterIsInstance().filter { it.ankan }.map { it.asMentsu() } + val anko: List + get() = menzenMentsu.filter { it.type == MentsuType.Kotsu } + + furo.filter { it.type == FuroType.Ankan }.map { it.asMentsu() } } /** diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/IHasFuro.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/IHasFuro.kt index da98a33..d97fdd6 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/IHasFuro.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/models/hand/IHasFuro.kt @@ -1,7 +1,7 @@ package mahjongutils.models.hand import mahjongutils.models.Furo -import mahjongutils.models.Kan +import mahjongutils.models.FuroType interface IHasFuro { /** @@ -13,5 +13,5 @@ interface IHasFuro { * 是否门清 */ val menzen: Boolean - get() = furo.all { it is Kan && it.ankan } + get() = furo.all { it.type == FuroType.Ankan } } \ No newline at end of file diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/FuroChanceShanten.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/FuroChanceShanten.kt index 419aed2..d11ad9d 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/FuroChanceShanten.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/FuroChanceShanten.kt @@ -1,7 +1,16 @@ package mahjongutils.shanten import mahjongutils.CalcContext -import mahjongutils.models.* +import mahjongutils.models.Chi +import mahjongutils.models.Kan +import mahjongutils.models.Kanchan +import mahjongutils.models.Penchan +import mahjongutils.models.Pon +import mahjongutils.models.Ryanmen +import mahjongutils.models.TatsuType +import mahjongutils.models.Tile +import mahjongutils.models.TileType +import mahjongutils.models.countAsCodeArray import mahjongutils.shanten.helpers.normalizeTiles import kotlin.math.min @@ -117,7 +126,7 @@ internal fun CalcContext.furoChanceShanten( val shantenAfterChi = regularShanten( InternalShantenArgs( tilesAfterChi, - listOf(Chi((tt.withWaiting(chanceTile) as Shuntsu).tile)), + listOf(Chi((tt.withWaiting(chanceTile)).tile)), calcAdvanceNum = calcAdvanceNum, bestShantenOnly = bestShantenOnly, allowAnkan = false @@ -126,9 +135,9 @@ internal fun CalcContext.furoChanceShanten( val shantenInfo = shantenAfterChi.shantenInfo.asWithGot if (!allowKuikae) { val discardToAdvance = shantenInfo.discardToAdvance.filterKeys { - it !== chanceTile && (tt !is Ryanmen || ( - chanceTile > tt.second && it !== tt.first.advance(-1) - || chanceTile < tt.second && it !== tt.second.advance(1)) + it != chanceTile && (tt.type != TatsuType.Ryanmen || ( + chanceTile > tt.second && it != tt.first.advance(-1) + || chanceTile < tt.second && it != tt.second.advance(1)) ) } shantenInfo.copy( diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/RegularShanten.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/RegularShanten.kt index 24d4470..b2d53e7 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/RegularShanten.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/shanten/RegularShanten.kt @@ -1,6 +1,7 @@ package mahjongutils.shanten import mahjongutils.CalcContext +import mahjongutils.models.Ankan import mahjongutils.models.Furo import mahjongutils.models.Kan import mahjongutils.models.Tile @@ -316,7 +317,7 @@ private fun handleRegularShantenWithGot( val count = tilesCount[t.code] if (count == 4) { val tilesAfterAnkan = tiles - t - t - t - t - val furoAfterAnkan = furo + Kan(t, true) + val furoAfterAnkan = furo + Ankan(t) this[t] = handleRegularShantenWithoutGot( tilesAfterAnkan, furoAfterAnkan, calcGoodShapeAdvance, calcImprovement diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/CheckerFactory.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/CheckerFactory.kt index 9bb69e0..fc3196f 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/CheckerFactory.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/CheckerFactory.kt @@ -19,7 +19,7 @@ internal fun yakuhaiCheckerFactory( } (tile ?: tileFunc?.invoke(pattern))?.let { tile_ -> - pattern.mentsu.filterIsInstance().any { it.tile == tile_ } + pattern.mentsu.filter { it.type == MentsuType.Kotsu }.any { it.tile == tile_ } } ?: false } } @@ -44,15 +44,15 @@ internal fun yaochuSeriesCheckerFactory(shuntsu: Boolean, z: Boolean): YakuCheck var hasZ = pattern.jyantou.type == TileType.Z for (mt in pattern.mentsu) { - when (mt) { - is Shuntsu -> { + when (mt.type) { + MentsuType.Shuntsu -> { if (mt.tile.num != 1 && mt.tile.num != 7) { return@YakuChecker false } shuntsuCnt += 1 } - is Kotsu -> { + MentsuType.Kotsu -> { if (!mt.tile.isYaochu) { return@YakuChecker false } @@ -124,7 +124,7 @@ internal fun pekoSeriesCheckerFactory(pekoCount: Int): YakuChecker { } var cnt = 0 - val shuntsu = pattern.menzenMentsu.filterIsInstance() + val shuntsu = pattern.menzenMentsu.filter { it.type == MentsuType.Shuntsu } for (i in shuntsu.indices) { for (j in i + 1 until shuntsu.size) { if (shuntsu[i] == shuntsu[j]) { @@ -152,7 +152,7 @@ internal fun ankoSeriesCheckerFactory(ankoCount: Int, tanki: Boolean? = null): Y var anko = pattern.anko.count() // 双碰听牌荣和,算一个明刻 - if (pattern.agariTatsu is Toitsu && !pattern.tsumo) { + if (pattern.agariTatsu?.type == TatsuType.Toitsu && !pattern.tsumo) { anko -= 1 } @@ -176,7 +176,7 @@ internal fun kantsuSeriesCheckerFactory(kanCount: Int): YakuChecker { return@YakuChecker false } - val kan = pattern.furo.count { it is Kan } + val kan = pattern.furo.count { it.type == FuroType.Kan || it.type == FuroType.Ankan } kan == kanCount } } @@ -194,7 +194,7 @@ internal fun sangenSeriesCheckerFactory(sangenKotsuCount: Int, sangenJyantou: Bo return@YakuChecker false } - val sangenKotsu = pattern.mentsu.count { it is Kotsu && it.tile.isSangen } + val sangenKotsu = pattern.mentsu.count { it.type == MentsuType.Kotsu && it.tile.isSangen } sangenKotsu == sangenKotsuCount && (sangenJyantou && pattern.jyantou.isSangen || !sangenJyantou && !pattern.jyantou.isSangen) } } @@ -212,7 +212,7 @@ internal fun sushiSeriesCheckerFactory(windKotsuCount: Int, windJyantou: Boolean return@YakuChecker false } - val sangenKotsu = pattern.mentsu.count { it is Kotsu && it.tile.isWind } + val sangenKotsu = pattern.mentsu.count { it.type == MentsuType.Kotsu && it.tile.isWind } sangenKotsu == windKotsuCount && (windJyantou && pattern.jyantou.isWind || !windJyantou && !pattern.jyantou.isWind) } } diff --git a/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/Yakus.kt b/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/Yakus.kt index e68181c..a4c1292 100644 --- a/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/Yakus.kt +++ b/mahjong-utils/src/commonMain/kotlin/mahjongutils/yaku/Yakus.kt @@ -5,7 +5,10 @@ import mahjongutils.hora.HoraOptions import mahjongutils.hora.KokushiHoraHandPattern import mahjongutils.hora.RegularHoraHandPattern import mahjongutils.hora.helpers.calcHu -import mahjongutils.models.* +import mahjongutils.models.MentsuType +import mahjongutils.models.Tile +import mahjongutils.models.TileType +import mahjongutils.models.isYaochu /** * 包含所有役种 @@ -78,7 +81,7 @@ open class Yakus(val options: HoraOptions) { if (pattern !is RegularHoraHandPattern) return@Yaku false - val shuntsu = pattern.mentsu.filterIsInstance() + val shuntsu = pattern.mentsu.filter { it.type == MentsuType.Shuntsu } for (i in shuntsu.indices) { for (j in i + 1 until shuntsu.size) { for (k in j + 1 until shuntsu.size) { @@ -108,7 +111,7 @@ open class Yakus(val options: HoraOptions) { if (pattern !is RegularHoraHandPattern) return@Yaku false - val shuntsu = pattern.mentsu.filterIsInstance() + val shuntsu = pattern.mentsu.filter { it.type == MentsuType.Shuntsu } for (i in shuntsu.indices) { for (j in i + 1 until shuntsu.size) { for (k in j + 1 until shuntsu.size) { @@ -148,7 +151,7 @@ open class Yakus(val options: HoraOptions) { if (pattern !is RegularHoraHandPattern) return@Yaku false - if (pattern.mentsu.count { it is Shuntsu } != 0) { + if (pattern.mentsu.count { it.type == MentsuType.Shuntsu } != 0) { false } else { !Suanko.check(pattern) && !SuankoTanki.check(pattern) // 非四暗刻的情况 @@ -176,7 +179,7 @@ open class Yakus(val options: HoraOptions) { if (pattern !is RegularHoraHandPattern) return@Yaku false - val kotsu = pattern.mentsu.filterIsInstance() + val kotsu = pattern.mentsu.filter { it.type == MentsuType.Kotsu } for (i in kotsu.indices) { for (j in i + 1 until kotsu.size) { for (k in j + 1 until kotsu.size) { diff --git a/mahjong-utils/src/commonTest/kotlin/mahjongutils/shanten/TestFuroChanceShanten.kt b/mahjong-utils/src/commonTest/kotlin/mahjongutils/shanten/TestFuroChanceShanten.kt index bc86b4d..c568aa2 100644 --- a/mahjong-utils/src/commonTest/kotlin/mahjongutils/shanten/TestFuroChanceShanten.kt +++ b/mahjong-utils/src/commonTest/kotlin/mahjongutils/shanten/TestFuroChanceShanten.kt @@ -29,7 +29,7 @@ class TestFuroChanceShanten { shantenNum = 0, advance = setOf(), advanceNum = 0, - improvement = Tile.allExcludeAkaDora.filter { it !== Tile.get("9m") }.associateWith { + improvement = Tile.allExcludeAkaDora.filter { it != Tile.get("9m") }.associateWith { listOf( Improvement( discard = Tile.get("9m"),