From bc8debb2ccf07c78f12aef52bd68b1ee992ef358 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Tue, 31 Jul 2012 23:44:52 -0400 Subject: [PATCH] Added type specs and basic documentation --- .gitignore | 1 + Makefile | 2 ++ include/riichi.hrl | 2 ++ src/riichi.erl | 14 +++++++++++++- src/riichi_game.erl | 6 ++++++ src/riichi_hand.erl | 19 +++++++++++++++++-- src/yaku.erl | 23 ++++++++++++++++++++--- 7 files changed, 61 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 98bdf4c..5cdcbcd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.beam *~ ebin/* +doc/* .eunit/ diff --git a/Makefile b/Makefile index c62f248..2504355 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ REBAR=./rebar all: deps compile +docs: + @$(REBAR) doc deps: @$(REBAR) get-deps compile: deps diff --git a/include/riichi.hrl b/include/riichi.hrl index 3c1e76e..dfdb409 100644 --- a/include/riichi.hrl +++ b/include/riichi.hrl @@ -6,6 +6,7 @@ -define(TILES, ?SIMPLES ++ ?TERMINALS ++ ?HONOURS). -type wind() :: east | south | west | north. + -type dragon() :: green | red | white. -record(tile, { @@ -50,3 +51,4 @@ uradora :: [tile()], players :: [player()] }). +-type game() :: #game{}. diff --git a/src/riichi.erl b/src/riichi.erl index 389828d..bdc3961 100644 --- a/src/riichi.erl +++ b/src/riichi.erl @@ -1,3 +1,7 @@ +%% @author Correl Roush +%% +%% @doc Riichi Mahjong library. + -module(riichi). -include("../include/riichi.hrl"). @@ -14,6 +18,7 @@ tiles/0 ]). +-spec is_valid_tile(tile()) -> boolean(). is_valid_tile(#tile{suit=dragon, value=Value}) -> lists:member(Value, [white, green, red]); is_valid_tile(#tile{suit=wind, value=Value}) -> @@ -25,6 +30,7 @@ is_valid_tile(#tile{suit=Suit, value=Value}) -> is_valid_tile(_) -> false. +-spec dora(tile()) -> tile(). dora(#tile{suit=dragon, value=Value}=Indicator) -> case Value of white -> Indicator#tile{value=green}; @@ -49,12 +55,13 @@ dora(#tile{value=Value}=Indicator) -> end end. +-spec nearest(integer(), integer()) -> integer(). nearest(Num, To) when Num rem To == 0 -> Num; nearest(Num, To) -> Num - (Num rem To) + To. - +-spec score(integer(), integer(), boolean()) -> integer(). score(_Fu, Han, Limit) when (Han >= 5) and (Limit == true) -> if Han < 6 -> @@ -77,12 +84,14 @@ score(Fu, Han, Limit) -> Score end. +-spec score_hand(hand()) -> integer(). score_hand(#hand{}=H) -> score_hand(H, 20). score_hand(#hand{}=H, Fu) -> score_hand(H, Fu, true). +-spec score_hand(hand(), integer(), boolean()) -> integer(). score_hand(#hand{}=Hand, BaseFu, Limit) -> Fu = [ BaseFu @@ -128,13 +137,16 @@ score_hand(#hand{}=Hand, BaseFu, Limit) -> ], score(lists:sum(Fu), lists:sum([F(Hand) || F <- Yakuman]), Limit). +-spec find_sets([tile()]) -> [{integer(), tile()}]. find_sets(Tiles) -> Unique = sets:to_list(sets:from_list(Tiles)), [{length(lists:filter(fun(X) -> X == T end, Tiles)), T} || T <- Unique]. +-spec shuffle(list()) -> list(). shuffle(List) -> [X || {_, X} <- lists:sort([{random:uniform(), I} || I <- List])]. +-spec tiles() -> [string()]. tiles() -> lists:flatten([lists:duplicate(4, T) || T <- ?TILES]). diff --git a/src/riichi_game.erl b/src/riichi_game.erl index 0f285fb..7e4f0ae 100644 --- a/src/riichi_game.erl +++ b/src/riichi_game.erl @@ -1,9 +1,15 @@ +%% @author Correl Roush +%% +%% @doc Riichi Mahjong library. + -module(riichi_game). -include("../include/riichi.hrl"). -export([new/0]). +%% @doc Creates a new mahjong game, with all 136 tiles shuffled and organized. +-spec new() -> game(). new() -> Tiles = riichi:shuffle(riichi:tiles()), #game{rinshan=lists:sublist(Tiles, 1, 4), diff --git a/src/riichi_hand.erl b/src/riichi_hand.erl index d47d74d..ea70b20 100644 --- a/src/riichi_hand.erl +++ b/src/riichi_hand.erl @@ -1,15 +1,26 @@ +%% @author Correl Roush +%% +%% @doc Riichi Mahjong library. + -module(riichi_hand). -include("../include/riichi.hrl"). --compile([export_all]). +-export([find/1, + tiles/1, + head/1, + is_complete/1, + waits/1]). +-spec find + (hand()) -> [hand()]; + ([tile()]) -> [hand()]. find(#hand{} = Hand) -> - %find(lists:sort(Tiles), Hand#hand{tiles=[]}, []); find(lists:sort(tiles(Hand))); find(Tiles) -> find(lists:sort(Tiles), #hand{}, []). +-spec find([tile()], hand(), [hand()]) -> [hand()]. find([], Hand, Possible) -> [Hand|Possible]; @@ -39,9 +50,11 @@ find(Tiles, Hand = #hand{tiles=HT, melds=HM}, Possible) -> find(Rest, Hand#hand{tiles=[T|HT]}, Possible) end. +-spec tiles(hand()) -> [tile()]. tiles(#hand{tiles=Tiles, melds=Melds}) -> lists:flatten([TS || #meld{tiles=TS} <- Melds]) ++ Tiles. +-spec head(hand()) -> meld() | none. head(#hand{melds=Melds}) -> case [M || M = #meld{type=pair} <- Melds] of [Pair|_] -> @@ -50,6 +63,7 @@ head(#hand{melds=Melds}) -> none end. +-spec is_complete(hand()) -> boolean(). is_complete(#hand{tiles=[], melds=Melds}=Hand) -> Pairs = [M || M <- Melds, M#meld.type =:= pair], case length(Pairs) of @@ -65,6 +79,7 @@ is_complete(#hand{tiles=[], melds=Melds}=Hand) -> is_complete(#hand{}=Hand) -> yaku:kokushi_musou(#game{}, #player{hand=Hand}). +-spec waits(hand()) -> [tile()]. waits(#hand{}=Hand) -> [T || T <- ?TILES, 0 < length([H || H <- find(tiles(Hand) ++ [T]), diff --git a/src/yaku.erl b/src/yaku.erl index 82949d2..094b40a 100644 --- a/src/yaku.erl +++ b/src/yaku.erl @@ -1,3 +1,7 @@ +%% @author Correl Roush +%% +%% @doc Riichi Mahjong library. + -module(yaku). -include("../include/riichi.hrl"). @@ -8,6 +12,9 @@ chiitoitsu/2, kokushi_musou/2]). +%% @doc Counts the pons/kans of value tiles in a player's hand. +%% Value tiles include all of the dragons, plus the round wind and the player's seat wind. +-spec yakuhai(game(), player()) -> integer(). yakuhai(#game{round=Round}, #player{seat=Seat, hand=#hand{melds=Melds}}) -> length(lists:filter(fun(#meld{type=Type, tiles=[T|_]}) -> case {Type, T} of @@ -26,7 +33,9 @@ yakuhai(#game{round=Round}, #player{seat=Seat, hand=#hand{melds=Melds}}) -> end end, Melds)). - +%% @doc Returns true if the hand consists only of simple tiles. +%% Terminals, winds and dragons are not allowed. +-spec tanyao(game(), player()) -> boolean(). tanyao(#game{}, #player{hand=Hand}) -> not lists:any(fun(T = #tile{}) -> case T#tile.suit of @@ -40,6 +49,10 @@ tanyao(#game{}, #player{hand=Hand}) -> end, riichi_hand:tiles(Hand)). +%% @doc Returns true for a no-points hand. +%% To qualify for pinfu, the hand must be fully concealed, contain no pons/kans, +%% contain no dragons, round winds or seat winds, and must be won on an open wait. +-spec pinfu(game(), player()) -> boolean(). pinfu(#game{round=Round}, #player{seat=Seat, hand=Hand=#hand{melds=Melds}, drawn={_, Drawn}}) -> Closed = lists:all(fun(T) -> T#tile.from =:= draw end, riichi_hand:tiles(Hand)), OpenWait = length(riichi_hand:waits(#hand{tiles=riichi_hand:tiles(Hand) -- [Drawn]})) > 1, @@ -50,13 +63,17 @@ pinfu(#game{round=Round}, #player{seat=Seat, hand=Hand=#hand{melds=Melds}, drawn andalso HeadTile#tile.suit =/= dragon, Closed and OpenWait and Chiis and NonValuePair. -% 7 Pairs +%% @doc Returns true for a 7-pair hand. +-spec chiitoitsu(game(), player()) -> boolean(). chiitoitsu(#game{}, #player{hand=#hand{tiles=[], melds=Melds}}) when length(Melds) =:= 7 -> Pairs = [S || S <- Melds, S#meld.type =:= pair], length(Pairs) =:= 7 andalso sets:size(sets:from_list(Pairs)) =:= 7. -% 13 Orphans +%% @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. +-spec kokushi_musou(game(), player()) -> boolean(). kokushi_musou(#game{}, #player{hand=#hand{tiles=Tiles, melds=[#meld{type=pair, tiles=[T,T]}]}}) -> not lists:any(fun(#tile{value=V}) -> lists:member(V, lists:seq(2,8))