diff --git a/src/yaku.erl b/src/yaku.erl index 760edbf..ca114bc 100644 --- a/src/yaku.erl +++ b/src/yaku.erl @@ -20,6 +20,12 @@ san_kan_tsu/2, toi_toi/2, san_an_kou/2, + shou_san_gen/2, + honrouto/2, + honitsu/2, + jun_chan/2, + ryanpeikou/2, + chinitsu/2, kokushi_musou/2, ryuu_iisou/2, dai_san_gen/2]). @@ -91,8 +97,8 @@ count_unique(L) -> -spec iipeikou(game(), player()) -> boolean(). iipeikou(#game{}, #player{hand=#hand{melds=Melds}}) -> Chiis = [M || M = #meld{type=chii} <- Melds], - Counts = [C || {_, C} <- count_unique(Chiis)], - lists:max(Counts) > 1 andalso lists:max(Counts) < 4. + Counts = [C || {_, C} <- count_unique(Chiis), C == 2], + length(Counts) == 1. %% @doc Returns true for a Chanta hand %% All melds and the pair must include a terminal or honor tile @@ -164,6 +170,23 @@ san_an_kou(#game{}, #player{hand=#hand{melds=Melds}}) -> lists:member(T, [pon,kan])], length(ClosedPons) =:= 3. +%% @doc Returns true for a Shou san gen hand +shou_san_gen(#game{}, #player{hand=#hand{melds=Melds}}) -> + Pons = [M || M = #meld{type=Type, tiles=[#tile{value=Value}|_]} <- Melds, + lists:member(Type, [pon, kan]), + lists:member(Value, [red, green, white])], + Pairs = [M || M = #meld{type=Type, tiles=[#tile{value=Value}|_]} <- Melds, + Type == pair, + lists:member(Value, [red, green, white])], + length(Pons) == 2 andalso length(Pairs) == 1. + +%% @doc Returns true for a Honrouto hand +honrouto(#game{}, #player{hand=Hand}) -> + IsHonour = fun(T) -> + lists:member(T, ?HONOURS ++ ?TERMINALS) + end, + lists:all(IsHonour, riichi_hand:tiles(Hand)). + %% @doc Returns true for a 7-pair hand. -spec chiitoitsu(game(), player()) -> boolean(). chiitoitsu(#game{}, #player{hand=#hand{tiles=[], melds=Melds}}) @@ -171,6 +194,43 @@ chiitoitsu(#game{}, #player{hand=#hand{tiles=[], melds=Melds}}) Pairs = [S || S <- Melds, S#meld.type =:= pair], length(Pairs) =:= 7 andalso sets:size(sets:from_list(Pairs)) =:= 7. +%% @doc Returns true for a Honitsu hand. +honitsu(#game{}, #player{hand=Hand}) -> + Tiles = riichi_hand:tiles(Hand), + Suits = sets:to_list(sets:from_list([Suit || #tile{suit=Suit} <- Tiles, + lists:member(Suit, [pin, sou, man])])), + IsHonour = fun(T) -> + lists:member(T, ?HONOURS) + end, + length(Suits) == 1 andalso lists:any(IsHonour, Tiles). + +%% @doc Returns true for a Jun chan hand. +jun_chan(#game{}, #player{hand=#hand{melds=Melds}}) -> + IsTerminal = fun(T) -> + lists:member(T, ?TERMINALS) + end, + HasTerminal = fun(#meld{tiles=Tiles}) -> + lists:any(IsTerminal, Tiles) + end, + lists:all(HasTerminal, Melds). + +%% @doc Returns true for a Ryanpeikou hand. +ryanpeikou(#game{}, #player{hand=#hand{melds=Melds}}) -> + Chiis = [M || M = #meld{type=chii} <- Melds], + Counts = [C || {_, C} <- count_unique(Chiis), C == 2], + length(Counts) == 2. + +%% @doc Returns true for a Chinitsu hand. +chinitsu(#game{}, #player{hand=Hand}) -> + Tiles = riichi_hand:tiles(Hand), + Suits = sets:to_list(sets:from_list([Suit || #tile{suit=Suit} <- Tiles, + lists:member(Suit, [pin, sou, man])])), + IsHonour = fun(T) -> + lists:member(T, ?HONOURS) + end, + length(Suits) == 1 andalso not lists:any(IsHonour, Tiles). + + %% @doc Returns true for a 13 Orphans hand. %% The hand must contain one each of every terminal and honour tile, plus one %% additional tile matching any of the others in the hand. diff --git a/test/riichi_yaku_tests.erl b/test/riichi_yaku_tests.erl index 77b7dcc..2ce7ab1 100644 --- a/test/riichi_yaku_tests.erl +++ b/test/riichi_yaku_tests.erl @@ -44,6 +44,14 @@ iipeikou_test() -> #meld{type=chii, tiles=[#tile{suit=man, value=V} || V <- [6,7,8]]}]}, ?assertEqual(true, yaku:iipeikou(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=sou, value=6}}})). +ryanpeikou_not_iipeikou_test() -> + Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=sou, value=8})}, + #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}]}, + ?assertEqual(false, yaku:iipeikou(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=sou, value=6}}})). + chanta_test() -> Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=dragon, value=red})}, #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}, @@ -100,10 +108,58 @@ san_an_kou_test() -> #meld{type=chii, tiles=[#tile{suit=man, value=V} || V <- [6,7,8]]}]}, ?assertEqual(true, yaku:san_an_kou(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=dragon, value=red}}})). +shou_san_gen_test() -> + Hand = #hand{melds=[#meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [2,3,4]]}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=dragon, value=red})}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=dragon, value=green})}, + #meld{type=kan, tiles=lists:duplicate(4, #tile{suit=pin, value=1})}, + #meld{type=pair, tiles=lists:duplicate(2, #tile{suit=dragon, value=white})}]}, + ?assert(yaku:shou_san_gen(#game{}, #player{hand=Hand})). + +honrouto_test() -> + Hand = #hand{melds=[#meld{type=pon, tiles=lists:duplicate(3, #tile{suit=sou, value=1})}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=sou, value=9})}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=dragon, value=green})}, + #meld{type=kan, tiles=lists:duplicate(4, #tile{suit=pin, value=1})}, + #meld{type=pair, tiles=lists:duplicate(2, #tile{suit=dragon, value=white})}]}, + ?assert(yaku:honrouto(#game{}, #player{hand=Hand})). + chiitoitsu_test() -> Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=pin, value=V})} || V <- lists:seq(1,7)]}, ?assertEqual(true, yaku:chiitoitsu(#game{}, #player{hand=Hand})). +honitsu_test() -> + Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=sou, value=8})}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=dragon, value=red})}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=sou, value=1})}]}, + ?assertEqual(true, yaku:honitsu(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=sou, value=1}}})). + +jun_chan_test() -> + Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=sou, value=9})}, + #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [7,8,9]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [7,8,9]]}, + #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}]}, + ?assertEqual(true, yaku:jun_chan(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=sou, value=7}}})). + +ryanpeikou_test() -> + Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=sou, value=8})}, + #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=chii, tiles=[#tile{suit=pin, value=V} || V <- [1,2,3]]}]}, + ?assertEqual(true, yaku:ryanpeikou(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=sou, value=6}}})). + +chinitsu_test() -> + Hand = #hand{melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=sou, value=8})}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=sou, value=9})}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=chii, tiles=[#tile{suit=sou, value=V} || V <- [4,5,6]]}, + #meld{type=pon, tiles=lists:duplicate(3, #tile{suit=sou, value=1})}]}, + ?assertEqual(true, yaku:chinitsu(#game{}, #player{hand=Hand, drawn={tsumo, #tile{suit=sou, value=1}}})). + kokushi_musou_test() -> Hand = #hand{tiles=?TERMINALS ++ ?HONOURS -- [#tile{suit=pin, value=1}], melds=[#meld{type=pair, tiles=lists:duplicate(2, #tile{suit=pin, value=1})}]},