mirror of
https://github.com/correl/riichi.git
synced 2024-11-14 11:09:36 +00:00
Added type specs and basic documentation
This commit is contained in:
parent
fef579355b
commit
0b95361b7c
7 changed files with 61 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
*.beam
|
||||
*~
|
||||
ebin/*
|
||||
doc/*
|
||||
.eunit/
|
||||
|
|
2
Makefile
2
Makefile
|
@ -4,6 +4,8 @@ REBAR=./rebar
|
|||
|
||||
all: deps compile
|
||||
|
||||
docs:
|
||||
@$(REBAR) doc
|
||||
deps:
|
||||
@$(REBAR) get-deps
|
||||
compile: deps
|
||||
|
|
|
@ -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{}.
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
%% @author Correl Roush <correl@gmail.com>
|
||||
%%
|
||||
%% @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]).
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
%% @author Correl Roush <correl@gmail.com>
|
||||
%%
|
||||
%% @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),
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
%% @author Correl Roush <correl@gmail.com>
|
||||
%%
|
||||
%% @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]),
|
||||
|
|
23
src/yaku.erl
23
src/yaku.erl
|
@ -1,3 +1,7 @@
|
|||
%% @author Correl Roush <correl@gmail.com>
|
||||
%%
|
||||
%% @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))
|
||||
|
|
Loading…
Reference in a new issue