mirror of
https://github.com/correl/riichi.git
synced 2024-11-27 11:09:56 +00:00
Added type specs and basic documentation
This commit is contained in:
parent
0e7c551723
commit
bc8debb2cc
7 changed files with 61 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
*.beam
|
*.beam
|
||||||
*~
|
*~
|
||||||
ebin/*
|
ebin/*
|
||||||
|
doc/*
|
||||||
.eunit/
|
.eunit/
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -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
|
||||||
|
|
|
@ -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{}.
|
||||||
|
|
|
@ -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]).
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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]),
|
||||||
|
|
23
src/yaku.erl
23
src/yaku.erl
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue