diff --git a/include/riichi.hrl b/include/riichi.hrl index a8d044e..40a4ead 100644 --- a/include/riichi.hrl +++ b/include/riichi.hrl @@ -5,7 +5,8 @@ -record(hand, { tiles=[], - sets=[] + sets=[], + seqs=[] }). -record(set, { diff --git a/src/riichi_hand.erl b/src/riichi_hand.erl index c7419f1..5e37c88 100644 --- a/src/riichi_hand.erl +++ b/src/riichi_hand.erl @@ -1,43 +1,67 @@ -module(riichi_hand). --include("riichi.hrl"). +-include("../include/riichi.hrl"). -compile([export_all]). +find(Tiles) -> + find(lists:sort(Tiles), #hand{}, []). -find_sets(Tiles) -> - Unique = sets:to_list(sets:from_list(Tiles)), - [#set{count=length(lists:filter(fun(X) -> X == T end, Tiles)), tile=T, open=false} - || T <- Unique]. +find([], Hand, Possible) -> + [Hand|Possible]; -reorder_seqs(Tiles) -> - Unique = sets:to_list(sets:from_list(Tiles)), - lists:sort(Unique) ++ (Tiles -- Unique). +find(Tiles, Hand = #hand{tiles=HT, sets=HS}, Possible) -> + case Tiles of + [T, T, T, T|Rest] -> + find(Rest, Hand#hand{sets=[#set{count=4, tile=T, open=false}|HS]}, Possible); + _ -> [] + end ++ + case Tiles of + [T, T, T|Rest] -> + find(Rest, Hand#hand{sets=[#set{count=3, tile=T, open=false}|HS]}, Possible); + _ -> + [] + end ++ + case Tiles of + [T, T|Rest] -> + find(Rest, Hand#hand{sets=[#set{count=2, tile=T, open=false}|HS]}, Possible); + _ -> + [] + end ++ + case lists:sort(sets:to_list(sets:from_list(Tiles))) of + [T1 = #tile{value=V1, suit=S}, T2 = #tile{value=V2, suit=S}, T3 = #tile{value=V3, suit=S}|_] when + is_integer(V1) andalso + [V1, V2, V3] =:= [V1, V1 + 1, V1 + 2] -> + find(Tiles -- [T1, T2, T3], Hand#hand{sets=[#seq{tiles=[T1, T2, T3], open=false}|HS]}, Possible); + _ -> + [] + end ++ + case Tiles of + [T|Rest] -> + find(Rest, Hand#hand{tiles=[T|HT]}, Possible) + end. -find_seqs(Tiles) -> - find_seqs(reorder_seqs(Tiles), {[], []}). +is_complete(#hand{tiles=[], sets=Sets}) -> + Pairs = [S || S <- Sets, S#set.count =:= 2], + case length(Pairs) of + 1 -> + % Four mentsu + 1 pair = 5 sets + length(Sets) =:= 5; + 7 -> + % Must be seven *unique* pairs + sets:size(sets:from_list(Pairs)) =:= 7; + _ -> + false + end; +is_complete(#hand{}=Hand) -> + kokushi_musou(Hand). -find_seqs([], {Seqs, Rest}) -> - {lists:sort(Seqs), lists:sort(Rest)}; - -find_seqs([T1 = #tile{suit=Suit}, T2 = #tile{suit=Suit}, T3 = #tile{suit=Suit} | Tiles], {Seqs, Rest}) - when T2#tile.value =:= (T1#tile.value + 1) - andalso T3#tile.value =:= (T2#tile.value + 1) -> - find_seqs(reorder_seqs(Tiles), {[#seq{tiles=[T1, T2, T3], open=false} | Seqs], Rest}); - -find_seqs([T | Tiles], {Seqs, Rest}) -> - find_seqs(Tiles, {Seqs, [T | Rest]}). - -perms([]) -> [[]]; -perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])]. - -combinations(0, _) -> [[]]; -combinations(_, []) -> []; -combinations(N, [X|XS]) -> [[X|YS] || YS <- combinations(N-1, XS)] ++ combinations(N, XS). - -find_hands(Tiles) -> - find_hands(Tiles, []). -find_hands([], Hands) -> - Hands; -find_hands([_T | _Remaining] = Tiles, Hands) -> - [#hand{tiles=Tiles} | Hands]. +% 13 Orphans +kokushi_musou(#hand{tiles=Tiles, sets=Sets}) when + length(Tiles) =:= 13 + andalso length(Sets) =:= 0 -> + not lists:any(fun(#tile{value=V}) -> + lists:member(V, lists:seq(2,8)) + end, + Tiles) + andalso sets:size(sets:from_list(Tiles)) =:= 13. diff --git a/src/yaku.erl b/src/yaku.erl new file mode 100644 index 0000000..79aaeb0 --- /dev/null +++ b/src/yaku.erl @@ -0,0 +1,61 @@ +-module(yaku). + +-include("../include/riichi.hrl"). + +-compile([export_all]). + + +yakuhai(#hand{sets=Sets}) -> + length(lists:filter(fun(T = #tile{}) -> + case T#tile.suit of + wind -> + % TODO: Round/Seat Winds + false; + dragon -> + true; + _ -> + false + end + end, + Sets)). + +tanyao(#hand{sets=Sets}) -> + not lists:any(fun(T = #tile{}) -> + case T#tile.suit of + dragon -> + true; + wind -> + true; + _ -> + lists:member(T#tile.value, [1,9]) + end + end, + Sets). + +pinfu(#hand{sets=Sets}) -> + % TODO: Verify closed, open wait, pair not round/seat wind + lists:all(fun(S) -> + case S of + #seq{} -> + true; + _ -> + false + end + end, + [S || S <- Sets]). + +% 7 Pairs +chiitoitsu(#hand{tiles=[], sets=Sets}) + when length(Sets) =:= 7 -> + Pairs = [S || S <- Sets, S#set.count =:= 2], + length(Pairs) =:= 7 andalso sets:size(sets:from_list(Pairs)) =:= 7. + +% 13 Orphans +kokushi_musou(#hand{tiles=Tiles, sets=Sets}) + when length(Tiles) =:= 13 + andalso length(Sets) =:= 0 -> + not lists:any(fun(#tile{value=V}) -> + lists:member(V, lists:seq(2,8)) + end, + Tiles) + andalso sets:size(sets:from_list(Tiles)) =:= 13. diff --git a/test/riichi_hand_tests.erl b/test/riichi_hand_tests.erl index 1a8eb28..ad6816a 100644 --- a/test/riichi_hand_tests.erl +++ b/test/riichi_hand_tests.erl @@ -3,22 +3,9 @@ -include("riichi.hrl"). -include_lib("eunit/include/eunit.hrl"). -find_sets_test() -> - Tiles = [#tile{suit=man, value=V} || V <- lists:seq(1,8)], - Expected = {[ - #seq{tiles=[#tile{suit=man, value=V} || V <- lists:seq(1,3)], open=false}, - #seq{tiles=[#tile{suit=man, value=V} || V <- lists:seq(4,6)], open=false} - ], - [#tile{suit=man, value=V} || V <- lists:seq(7,8)] - }, - ?assertEqual(Expected, riichi_hand:find_seqs(Tiles)). -find_duplicate_sets_test() -> - Tiles = [#tile{suit=man, value=V} || V <- lists:seq(1,3), _ <- lists:seq(1,3)] ++ - [#tile{suit=man, value=4} || _ <- [1,2]], - Expected = {[ - #seq{tiles=[#tile{suit=man, value=V} || V <- lists:seq(1,3)], open=false} - || _ <- [1,2,3] - ], - [#tile{suit=man, value=4} || _ <- [1,2]] - }, - ?assertEqual(Expected, riichi_hand:find_seqs(Tiles)). +find_hands_test() -> + Tiles = [#tile{value=N, suit=pin} || N <- [1,2,3], _ <- [1,2]], + SetHand = #hand{sets=[#set{count=2, tile=#tile{value=N, suit=pin}, open=false} || N <- [3,2,1]]}, + SeqHand = #hand{sets=[#seq{tiles=[#tile{value=N, suit=pin} || N <- [1,2,3]], open=false} || _ <- [1,2]]}, + Found = [H || H <- riichi_hand:find(Tiles), H#hand.tiles =:= []], + ?assertEqual([SetHand, SeqHand], Found).