@@ -68,7 +68,7 @@ export const YAKUMAN_NAMES = [
'chiho' ,
] as const ;
type NormalYakuName = typeof NORMAL_YAKU_NAMES [ number ]
type NormalYakuName = typeof NORMAL_YAKU_NAMES [ number ] ;
type YakumanName = typeof YAKUMAN_NAMES [ number ] ;
@@ -302,18 +302,19 @@ function countIndenticalMentsuPairs(mentsus: [TileType, TileType, TileType][]) {
* 暗刻の数を数える (三暗刻なら3、四暗刻なら4)
*/
function countAnkos ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantouWithWait ) {
le t ankans = state . huros . filter ( huro = > huro . type == 'ankan' ) . length ;
cons t ankans = state . huros . filter ( huro = > huro . type === 'ankan' ) . length ;
const handKotsus = fourMentsuOneJyantou . mentsus . filter ( mentsu = > isKotsu ( mentsu ) ) . length ;
// ロンによりできた刻子は暗刻ではない
if ( state . ronTile != null && fourMentsuOneJyantou . waitedFor == 'mentsu' && isToitsu ( fourMentsuOneJyantou . waitedTaatsu ) ) {
if ( state . ronTile != null && fourMentsuOneJyantou . waitedFor === 'mentsu' && isToitsu ( fourMentsuOneJyantou . waitedTaatsu ) ) {
return ankans + handKotsus - 1 ;
}
return ankans + handKotsus ;
}
export const NORMAL_YAKU_DEFINITIONS : NormalYakuDefinition [ ] = [ {
export const NORMAL_YAKU_DEFINITIONS : NormalYakuDefinition [ ] = [
{
name : 'tsumo' ,
fan : 1 ,
isYakuman : false ,
@@ -323,48 +324,54 @@ export const NORMAL_YAKU_DEFINITIONS: NormalYakuDefinition[] = [{
return state . tsumoTile != null ;
} ,
} , {
} ,
{
name : 'riichi' ,
fan : 1 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return ! state . doubleRiichi && ( state . riichi ? ? false ) ;
} ,
} , {
} ,
{
name : 'double-riichi' ,
fan : 2 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku ) = > {
return state . doubleRiichi ? ? false ;
}
} , {
} ,
} ,
{
name : 'ippatsu' ,
fan : 1 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return state . ippatsu ? ? false ;
} ,
} , {
} ,
{
name : 'rinshan' ,
fan : 1 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return ( state . tsumoTile != null && state . rinshan ) ? ? false ;
}
} , {
} ,
} ,
{
name : 'haitei' ,
fan : 1 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return ( state . tsumoTile != null && state . haitei ) ? ? false ;
}
} , {
} ,
} ,
{
name : 'hotei' ,
fan : 1 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return ( state . ronTile != null && state . hotei ) ? ? false ;
}
} ,
} ,
new Yakuhai ( 'red' , 'chun' ) ,
new Yakuhai ( 'white' , 'haku' ) ,
@@ -392,7 +399,8 @@ new SeatWind('seat-wind-n', 'n'),
false ) . length === 0 )
) ;
} ,
} , {
} ,
{
name : 'pinfu' ,
fan : 1 ,
isYakuman : false ,
@@ -405,7 +413,7 @@ new SeatWind('seat-wind-n', 'n'),
if ( state . handTiles . some ( t = > [ 'haku' , 'hatsu' , 'chun' ] . includes ( t ) ) ) return false ;
// 両面待ちかどうか
if ( ! ( fourMentsuOneJyantou != null && fourMentsuOneJyantou . waitedFor == 'mentsu' && isRyanmen ( fourMentsuOneJyantou . waitedTaatsu ) ) ) return false ;
if ( ! ( fourMentsuOneJyantou != null && fourMentsuOneJyantou . waitedFor === 'mentsu' && isRyanmen ( fourMentsuOneJyantou . waitedTaatsu ) ) ) return false ;
// 風牌判定(役牌でなければOK)
if ( fourMentsuOneJyantou . head === state . seatWind ) return false ;
@@ -416,7 +424,8 @@ new SeatWind('seat-wind-n', 'n'),
return true ;
} ,
} , {
} ,
{
name : 'honitsu' ,
fan : 3 ,
isYakuman : false ,
@@ -443,7 +452,8 @@ new SeatWind('seat-wind-n', 'n'),
return true ;
} ,
} , {
} ,
{
name : 'chinitsu' ,
fan : 6 ,
isYakuman : false ,
@@ -470,7 +480,8 @@ new SeatWind('seat-wind-n', 'n'),
return true ;
} ,
} , {
} ,
{
name : 'iipeko' ,
fan : 1 ,
isYakuman : false ,
@@ -481,9 +492,10 @@ new SeatWind('seat-wind-n', 'n'),
if ( state . huros . some ( huro = > includes ( CALL_HURO_TYPES , huro . type ) ) ) return false ;
// 同じ順子が2つあるか?
return countIndenticalMentsuPairs ( fourMentsuOneJyantou . mentsus ) == 1 ;
return countIndenticalMentsuPairs ( fourMentsuOneJyantou . mentsus ) === 1 ;
} ,
} , {
} ,
{
name : 'ryampeko' ,
fan : 3 ,
isYakuman : false ,
@@ -494,9 +506,10 @@ new SeatWind('seat-wind-n', 'n'),
if ( state . huros . some ( huro = > includes ( CALL_HURO_TYPES , huro . type ) ) ) return false ;
// 2つの同じ順子が2組あるか?
return countIndenticalMentsuPairs ( fourMentsuOneJyantou . mentsus ) == 2 ;
return countIndenticalMentsuPairs ( fourMentsuOneJyantou . mentsus ) === 2 ;
} ,
} , {
} ,
{
name : 'toitoi' ,
fan : 2 ,
isYakuman : false ,
@@ -512,30 +525,34 @@ new SeatWind('seat-wind-n', 'n'),
return true ;
} ,
} , {
} ,
{
name : 'sananko' ,
fan : 2 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantouWithWait | null ) = > {
return fourMentsuOneJyantou != null && countAnkos ( state , fourMentsuOneJyantou ) == 3 ;
return fourMentsuOneJyantou != null && countAnkos ( state , fourMentsuOneJyantou ) === 3 ;
} ,
} , {
} ,
{
name : 'honroto' ,
fan : 2 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku ) = > {
return state . huros . every ( huro = > huro . type != 'cii' && includes ( YAOCHU_TILES , huro . tile ) ) &&
return state . huros . every ( huro = > huro . type !== 'cii' && includes ( YAOCHU_TILES , huro . tile ) ) &&
state . handTiles . every ( tile = > includes ( YAOCHU_TILES , tile ) ) ;
}
} , {
} ,
} ,
{
name : 'sankantsu' ,
fan : 2 ,
isYakuman : false ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return fourMentsuOneJyantou != null &&
state . huros . filter ( huro = > huro . type == 'ankan' || huro . type == 'minkan' ) . length == 3 ;
}
} , {
state . huros . filter ( huro = > huro . type === 'ankan' || huro . type === 'minkan' ) . length === 3 ;
} ,
} ,
{
name : 'sanshoku-dojun' ,
fan : 2 ,
isYakuman : false ,
@@ -569,7 +586,8 @@ new SeatWind('seat-wind-n', 'n'),
return false ;
} ,
} , {
} ,
{
name : 'sanshoku-doko' ,
fan : 2 ,
isYakuman : false ,
@@ -603,7 +621,8 @@ new SeatWind('seat-wind-n', 'n'),
return false ;
} ,
} , {
} ,
{
name : 'ittsu' ,
fan : 2 ,
isYakuman : false ,
@@ -612,7 +631,7 @@ new SeatWind('seat-wind-n', 'n'),
if ( fourMentsuOneJyantou == null ) return false ;
const shuntsus = fourMentsuOneJyantou . mentsus . filter ( tiles = > isShuntu ( tiles ) ) ;
shuntsus . push ( . . . state . huros . filter ( ( huro ) : huro is Cii = > huro . type == 'cii' ) . map ( huro = > huro . tiles ) ) ;
shuntsus . push ( . . . state . huros . filter ( ( huro ) : huro is Cii = > huro . type === 'cii' ) . map ( huro = > huro . tiles ) ) ;
if ( shuntsus . some ( tiles = > tiles [ 0 ] === 'm1' && tiles [ 1 ] === 'm2' && tiles [ 2 ] === 'm3' ) ) {
if ( shuntsus . some ( tiles = > tiles [ 0 ] === 'm4' && tiles [ 1 ] === 'm5' && tiles [ 2 ] === 'm6' ) ) {
@@ -638,7 +657,8 @@ new SeatWind('seat-wind-n', 'n'),
return false ;
} ,
} , {
} ,
{
name : 'chanta' ,
fan : 2 ,
isYakuman : false ,
@@ -658,15 +678,16 @@ new SeatWind('seat-wind-n', 'n'),
// いずれかの雀頭か面子に字牌を含まないとダメ
if ( ! ( includes ( CHAR_TILES , head ) ||
mentsus . some ( mentsu = > includes ( CHAR_TILES , mentsu [ 0 ] ) ) ||
huros . some ( huro = > huro . type != 'cii' && includes ( CHAR_TILES , huro . tile ) ) ) ) return false ;
huros . some ( huro = > huro . type !== 'cii' && includes ( CHAR_TILES , huro . tile ) ) ) ) return false ;
// 全ての面子に幺九牌が含まれる
return ( mentsus . every ( mentsu = > mentsu . some ( tile = > includes ( YAOCHU_TILES , tile ) ) ) &&
huros . every ( huro = > huro . type == 'cii' ?
huros . every ( huro = > huro . type === 'cii' ?
huro . tiles . some ( tile = > includes ( YAOCHU_TILES , tile ) ) :
includes ( YAOCHU_TILES , huro . tile ) ) ) ;
} ,
} , {
} ,
{
name : 'junchan' ,
fan : 3 ,
isYakuman : false ,
@@ -685,11 +706,12 @@ new SeatWind('seat-wind-n', 'n'),
// 全ての面子に老頭牌が含まれる
return ( mentsus . every ( mentsu = > mentsu . some ( tile = > includes ( TERMINAL_TILES , tile ) ) ) &&
huros . every ( huro = > huro . type == 'cii' ?
huros . every ( huro = > huro . type === 'cii' ?
huro . tiles . some ( tile = > includes ( TERMINAL_TILES , tile ) ) :
includes ( TERMINAL_TILES , huro . tile ) ) ) ;
} ,
} , {
} ,
{
name : 'chitoitsu' ,
fan : 2 ,
isYakuman : false ,
@@ -703,7 +725,8 @@ new SeatWind('seat-wind-n', 'n'),
}
return Array . from ( countMap . values ( ) ) . every ( c = > c === 2 ) ;
} ,
} , {
} ,
{
name : 'shosangen' ,
fan : 2 ,
isYakuman : false ,
@@ -730,23 +753,27 @@ new SeatWind('seat-wind-n', 'n'),
return false ;
} ,
} ] ;
} ,
] ;
export const YAKUMAN_DEFINITIONS : YakumanDefinition [ ] = [ {
export const YAKUMAN_DEFINITIONS : YakumanDefinition [ ] = [
{
name : 'suanko-tanki' ,
isYakuman : true ,
isDoubleYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantouWithWait | null ) = > {
return fourMentsuOneJyantou != null && fourMentsuOneJyantou . waitedFor == 'head' && countAnkos ( state , fourMentsuOneJyantou ) == 4 ;
}
} , {
return fourMentsuOneJyantou != null && fourMentsuOneJyantou . waitedFor === 'head' && countAnkos ( state , fourMentsuOneJyantou ) === 4 ;
} ,
} ,
{
name : 'suanko' ,
isYakuman : true ,
upper : 'suanko-tanki' ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantouWithWait | null ) = > {
return fourMentsuOneJyantou != null && countAnkos ( state , fourMentsuOneJyantou ) == 4 ;
}
} , {
return fourMentsuOneJyantou != null && countAnkos ( state , fourMentsuOneJyantou ) === 4 ;
} ,
} ,
{
name : 'daisangen' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
@@ -766,7 +793,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return kotsuTiles . includes ( 'haku' ) && kotsuTiles . includes ( 'hatsu' ) && kotsuTiles . includes ( 'chun' ) ;
} ,
} , {
} ,
{
name : 'shosushi' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
@@ -792,7 +820,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return false ;
} ,
} , {
} ,
{
name : 'daisushi' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
@@ -812,7 +841,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return kotsuTiles . includes ( 'e' ) && kotsuTiles . includes ( 's' ) && kotsuTiles . includes ( 'w' ) && kotsuTiles . includes ( 'n' ) ;
} ,
} , {
} ,
{
name : 'tsuiso' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku ) = > {
@@ -832,7 +862,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return true ;
} ,
} , {
} ,
{
name : 'ryuiso' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
@@ -847,22 +878,25 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return true ;
} ,
} , {
} ,
{
name : 'chinroto' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return fourMentsuOneJyantou != null &&
state . huros . every ( huro = > huro . type != 'cii' && includes ( TERMINAL_TILES , huro . tile ) ) &&
state . huros . every ( huro = > huro . type !== 'cii' && includes ( TERMINAL_TILES , huro . tile ) ) &&
state . handTiles . every ( tile = > includes ( TERMINAL_TILES , tile ) ) ;
}
} , {
} ,
} ,
{
name : 'sukantsu' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return fourMentsuOneJyantou != null &&
state . huros . filter ( huro = > huro . type == 'ankan' || huro . type == 'minkan' ) . length == 4 ;
}
} , {
state . huros . filter ( huro = > huro . type === 'ankan' || huro . type === 'minkan' ) . length === 4 ;
} ,
} ,
{
name : 'churen-9' ,
isYakuman : true ,
isDoubleYakuman : true ,
@@ -901,7 +935,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return false ;
} ,
} , {
} ,
{
name : 'churen' ,
upper : 'churen-9' ,
isYakuman : true ,
@@ -933,34 +968,39 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return false ;
} ,
} , {
} ,
{
name : 'kokushi-13' ,
isYakuman : true ,
isDoubleYakuman : true ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
const agariTile = state . tsumoTile ? ? state . ronTile ;
return KOKUSHI_TILES . every ( t = > state . handTiles . includes ( t ) ) && countTiles ( state . handTiles , agariTile ) == 2 ;
}
} , {
return KOKUSHI_TILES . every ( t = > state . handTiles . includes ( t ) ) && countTiles ( state . handTiles , agariTile ) === 2 ;
} ,
} ,
{
name : 'kokushi' ,
isYakuman : true ,
upper : 'kokushi-13' ,
calc : ( state : EnvForCalcYaku , fourMentsuOneJyantou : FourMentsuOneJyantou | null ) = > {
return KOKUSHI_TILES . every ( t = > state . handTiles . includes ( t ) ) && KOKUSHI_TILES . some ( t = > countTiles ( state . handTiles , t ) == 2 ) ;
return KOKUSHI_TILES . every ( t = > state . handTiles . includes ( t ) ) && KOKUSHI_TILES . some ( t = > countTiles ( state . handTiles , t ) === 2 ) ;
} ,
} , {
} ,
{
name : 'tenho' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku ) = > {
return ( state . firstTurn ? ? false ) && state . tsumoTile != null && state . seatWind == 'e' ;
}
} , {
return ( state . firstTurn ? ? false ) && state . tsumoTile != null && state . seatWind === 'e' ;
} ,
} ,
{
name : 'chiho' ,
isYakuman : true ,
calc : ( state : EnvForCalcYaku ) = > {
return ( state . firstTurn ? ? false ) && state . tsumoTile != null && state . seatWind != 'e' ;
}
} ] ;
return ( state . firstTurn ? ? false ) && state . tsumoTile != null && state . seatWind !== 'e' ;
} ,
} ,
] ;
export function convertHuroForCalcYaku ( huro : Huro ) : HuroForCalcYaku {
switch ( huro . type ) {
@@ -970,7 +1010,7 @@ export function convertHuroForCalcYaku(huro: Huro): HuroForCalcYaku {
return {
type : huro . type ,
tile : TILE_ID_MAP.get ( huro . tiles [ 0 ] ) ! . t ,
}
} ;
case 'cii' :
return {
type : 'cii' ,
@@ -986,7 +1026,7 @@ const NORMAL_YAKU_DATA_MAP = new Map<NormalYakuName, Required<NormalYakuData>>(
fan : yaku.fan ,
isYakuman : false ,
kuisagari : yaku.kuisagari ? ? false ,
} ] as const )
} ] as const ) ,
) ;
const YAKUMAN_DATA_MAP = new Map < YakuName , Required < YakumanData > > (
@@ -996,7 +1036,7 @@ const YAKUMAN_DATA_MAP = new Map<YakuName, Required<YakumanData>>(
fan : null ,
isYakuman : true ,
isDoubleYakuman : yaku.isDoubleYakuman ? ? false ,
} ] )
} ] ) ,
) ;
export function calcYakusWithDetail ( state : EnvForCalcYaku ) : YakuSet {
@@ -1009,7 +1049,7 @@ export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
throw new TypeError ( 'Agari tile not included in hand tiles' ) ;
}
if ( state . handTiles . length + state . huros . length * 3 != 14 ) {
if ( state . handTiles . length + state . huros . length * 3 !== 14 ) {
throw new TypeError ( 'Invalid tile count' ) ;
}
@@ -1017,7 +1057,7 @@ export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
if ( oneHeadFourMentsuPatterns . length === 0 ) oneHeadFourMentsuPatterns . push ( null ) ;
const waitPatterns = oneHeadFourMentsuPatterns . map (
fourMentsuOneJyantou = > calcWaitPatterns ( fourMentsuOneJyantou , agariTile )
fourMentsuOneJyantou = > calcWaitPatterns ( fourMentsuOneJyantou , agariTile ) ,
) . flat ( ) ;
const yakumanPatterns = waitPatterns . map ( fourMentsuOneJyantouWithWait = > {
@@ -1038,13 +1078,13 @@ export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
const yakuPatterns = waitPatterns . map (
fourMentsuOneJyantouWithWait = > NORMAL_YAKU_DEFINITIONS . filter (
yakuDef = > yakuDef . calc ( state , fourMentsuOneJyantouWithWait )
) . map ( yakuDef = > NORMAL_YAKU_DATA_MAP . get ( yakuDef . name ) ! )
yakuDef = > yakuDef . calc ( state , fourMentsuOneJyantouWithWait ) ,
) . map ( yakuDef = > NORMAL_YAKU_DATA_MAP . get ( yakuDef . name ) ! ) ,
) . filter ( yakus = > yakus . length > 0 ) ;
const isMenzen = state . huros . some ( huro = > includes ( CALL_HURO_TYPES , huro . type ) ) ;
if ( yakuPatterns . length == 0 ) {
if ( yakuPatterns . length === 0 ) {
return new NormalYakuSet ( isMenzen , [ ] ) ;
}