Added type specs and basic documentation

This commit is contained in:
Correl Roush 2012-07-31 23:44:52 -04:00
parent fef579355b
commit 0b95361b7c
7 changed files with 61 additions and 6 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.beam *.beam
*~ *~
ebin/* ebin/*
doc/*
.eunit/ .eunit/

View file

@ -4,6 +4,8 @@ REBAR=./rebar
all: deps compile all: deps compile
docs:
@$(REBAR) doc
deps: deps:
@$(REBAR) get-deps @$(REBAR) get-deps
compile: deps compile: deps

View file

@ -6,6 +6,7 @@
-define(TILES, ?SIMPLES ++ ?TERMINALS ++ ?HONOURS). -define(TILES, ?SIMPLES ++ ?TERMINALS ++ ?HONOURS).
-type wind() :: east | south | west | north. -type wind() :: east | south | west | north.
-type dragon() :: green | red | white. -type dragon() :: green | red | white.
-record(tile, { -record(tile, {
@ -50,3 +51,4 @@
uradora :: [tile()], uradora :: [tile()],
players :: [player()] players :: [player()]
}). }).
-type game() :: #game{}.

View file

@ -1,3 +1,7 @@
%% @author Correl Roush <correl@gmail.com>
%%
%% @doc Riichi Mahjong library.
-module(riichi). -module(riichi).
-include("../include/riichi.hrl"). -include("../include/riichi.hrl").
@ -14,6 +18,7 @@
tiles/0 tiles/0
]). ]).
-spec is_valid_tile(tile()) -> boolean().
is_valid_tile(#tile{suit=dragon, value=Value}) -> is_valid_tile(#tile{suit=dragon, value=Value}) ->
lists:member(Value, [white, green, red]); lists:member(Value, [white, green, red]);
is_valid_tile(#tile{suit=wind, value=Value}) -> is_valid_tile(#tile{suit=wind, value=Value}) ->
@ -25,6 +30,7 @@ is_valid_tile(#tile{suit=Suit, value=Value}) ->
is_valid_tile(_) -> is_valid_tile(_) ->
false. false.
-spec dora(tile()) -> tile().
dora(#tile{suit=dragon, value=Value}=Indicator) -> dora(#tile{suit=dragon, value=Value}=Indicator) ->
case Value of case Value of
white -> Indicator#tile{value=green}; white -> Indicator#tile{value=green};
@ -49,12 +55,13 @@ dora(#tile{value=Value}=Indicator) ->
end end
end. end.
-spec nearest(integer(), integer()) -> integer().
nearest(Num, To) when Num rem To == 0 -> nearest(Num, To) when Num rem To == 0 ->
Num; Num;
nearest(Num, To) -> nearest(Num, To) ->
Num - (Num rem To) + To. Num - (Num rem To) + To.
-spec score(integer(), integer(), boolean()) -> integer().
score(_Fu, Han, Limit) when (Han >= 5) and (Limit == true) -> score(_Fu, Han, Limit) when (Han >= 5) and (Limit == true) ->
if if
Han < 6 -> Han < 6 ->
@ -77,12 +84,14 @@ score(Fu, Han, Limit) ->
Score Score
end. end.
-spec score_hand(hand()) -> integer().
score_hand(#hand{}=H) -> score_hand(#hand{}=H) ->
score_hand(H, 20). score_hand(H, 20).
score_hand(#hand{}=H, Fu) -> score_hand(#hand{}=H, Fu) ->
score_hand(H, Fu, true). score_hand(H, Fu, true).
-spec score_hand(hand(), integer(), boolean()) -> integer().
score_hand(#hand{}=Hand, BaseFu, Limit) -> score_hand(#hand{}=Hand, BaseFu, Limit) ->
Fu = [ Fu = [
BaseFu BaseFu
@ -128,13 +137,16 @@ score_hand(#hand{}=Hand, BaseFu, Limit) ->
], ],
score(lists:sum(Fu), lists:sum([F(Hand) || F <- Yakuman]), Limit). score(lists:sum(Fu), lists:sum([F(Hand) || F <- Yakuman]), Limit).
-spec find_sets([tile()]) -> [{integer(), tile()}].
find_sets(Tiles) -> find_sets(Tiles) ->
Unique = sets:to_list(sets:from_list(Tiles)), Unique = sets:to_list(sets:from_list(Tiles)),
[{length(lists:filter(fun(X) -> X == T end, Tiles)), T} [{length(lists:filter(fun(X) -> X == T end, Tiles)), T}
|| T <- Unique]. || T <- Unique].
-spec shuffle(list()) -> list().
shuffle(List) -> shuffle(List) ->
[X || {_, X} <- lists:sort([{random:uniform(), I} || I <- List])]. [X || {_, X} <- lists:sort([{random:uniform(), I} || I <- List])].
-spec tiles() -> [string()].
tiles() -> tiles() ->
lists:flatten([lists:duplicate(4, T) || T <- ?TILES]). lists:flatten([lists:duplicate(4, T) || T <- ?TILES]).

View file

@ -1,9 +1,15 @@
%% @author Correl Roush <correl@gmail.com>
%%
%% @doc Riichi Mahjong library.
-module(riichi_game). -module(riichi_game).
-include("../include/riichi.hrl"). -include("../include/riichi.hrl").
-export([new/0]). -export([new/0]).
%% @doc Creates a new mahjong game, with all 136 tiles shuffled and organized.
-spec new() -> game().
new() -> new() ->
Tiles = riichi:shuffle(riichi:tiles()), Tiles = riichi:shuffle(riichi:tiles()),
#game{rinshan=lists:sublist(Tiles, 1, 4), #game{rinshan=lists:sublist(Tiles, 1, 4),

View file

@ -1,15 +1,26 @@
%% @author Correl Roush <correl@gmail.com>
%%
%% @doc Riichi Mahjong library.
-module(riichi_hand). -module(riichi_hand).
-include("../include/riichi.hrl"). -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(#hand{} = Hand) ->
%find(lists:sort(Tiles), Hand#hand{tiles=[]}, []);
find(lists:sort(tiles(Hand))); find(lists:sort(tiles(Hand)));
find(Tiles) -> find(Tiles) ->
find(lists:sort(Tiles), #hand{}, []). find(lists:sort(Tiles), #hand{}, []).
-spec find([tile()], hand(), [hand()]) -> [hand()].
find([], Hand, Possible) -> find([], Hand, Possible) ->
[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) find(Rest, Hand#hand{tiles=[T|HT]}, Possible)
end. end.
-spec tiles(hand()) -> [tile()].
tiles(#hand{tiles=Tiles, melds=Melds}) -> tiles(#hand{tiles=Tiles, melds=Melds}) ->
lists:flatten([TS || #meld{tiles=TS} <- Melds]) ++ Tiles. lists:flatten([TS || #meld{tiles=TS} <- Melds]) ++ Tiles.
-spec head(hand()) -> meld() | none.
head(#hand{melds=Melds}) -> head(#hand{melds=Melds}) ->
case [M || M = #meld{type=pair} <- Melds] of case [M || M = #meld{type=pair} <- Melds] of
[Pair|_] -> [Pair|_] ->
@ -50,6 +63,7 @@ head(#hand{melds=Melds}) ->
none none
end. end.
-spec is_complete(hand()) -> boolean().
is_complete(#hand{tiles=[], melds=Melds}=Hand) -> is_complete(#hand{tiles=[], melds=Melds}=Hand) ->
Pairs = [M || M <- Melds, M#meld.type =:= pair], Pairs = [M || M <- Melds, M#meld.type =:= pair],
case length(Pairs) of case length(Pairs) of
@ -65,6 +79,7 @@ is_complete(#hand{tiles=[], melds=Melds}=Hand) ->
is_complete(#hand{}=Hand) -> is_complete(#hand{}=Hand) ->
yaku:kokushi_musou(#game{}, #player{hand=Hand}). yaku:kokushi_musou(#game{}, #player{hand=Hand}).
-spec waits(hand()) -> [tile()].
waits(#hand{}=Hand) -> waits(#hand{}=Hand) ->
[T || T <- ?TILES, [T || T <- ?TILES,
0 < length([H || H <- find(tiles(Hand) ++ [T]), 0 < length([H || H <- find(tiles(Hand) ++ [T]),

View file

@ -1,3 +1,7 @@
%% @author Correl Roush <correl@gmail.com>
%%
%% @doc Riichi Mahjong library.
-module(yaku). -module(yaku).
-include("../include/riichi.hrl"). -include("../include/riichi.hrl").
@ -8,6 +12,9 @@
chiitoitsu/2, chiitoitsu/2,
kokushi_musou/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}}) -> yakuhai(#game{round=Round}, #player{seat=Seat, hand=#hand{melds=Melds}}) ->
length(lists:filter(fun(#meld{type=Type, tiles=[T|_]}) -> length(lists:filter(fun(#meld{type=Type, tiles=[T|_]}) ->
case {Type, T} of case {Type, T} of
@ -26,7 +33,9 @@ yakuhai(#game{round=Round}, #player{seat=Seat, hand=#hand{melds=Melds}}) ->
end end
end, end,
Melds)). 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}) -> tanyao(#game{}, #player{hand=Hand}) ->
not lists:any(fun(T = #tile{}) -> not lists:any(fun(T = #tile{}) ->
case T#tile.suit of case T#tile.suit of
@ -40,6 +49,10 @@ tanyao(#game{}, #player{hand=Hand}) ->
end, end,
riichi_hand:tiles(Hand)). 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}}) -> 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)), 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, 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, andalso HeadTile#tile.suit =/= dragon,
Closed and OpenWait and Chiis and NonValuePair. 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}}) chiitoitsu(#game{}, #player{hand=#hand{tiles=[], melds=Melds}})
when length(Melds) =:= 7 -> when length(Melds) =:= 7 ->
Pairs = [S || S <- Melds, S#meld.type =:= pair], Pairs = [S || S <- Melds, S#meld.type =:= pair],
length(Pairs) =:= 7 andalso sets:size(sets:from_list(Pairs)) =:= 7. 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]}]}}) -> kokushi_musou(#game{}, #player{hand=#hand{tiles=Tiles, melds=[#meld{type=pair, tiles=[T,T]}]}}) ->
not lists:any(fun(#tile{value=V}) -> not lists:any(fun(#tile{value=V}) ->
lists:member(V, lists:seq(2,8)) lists:member(V, lists:seq(2,8))