-module(game_logic).
|
|
-export([validate_play/2, calculate_card_value/1, evaluate_hand_strength/1,
|
|
find_all_possible_patterns/1, analyze_card_pattern/1]).
|
|
|
|
-define(CARD_VALUES, #{
|
|
"3" => 3, "4" => 4, "5" => 5, "6" => 6, "7" => 7, "8" => 8, "9" => 9,
|
|
"10" => 10, "J" => 11, "Q" => 12, "K" => 13, "A" => 14, "2" => 15,
|
|
"小王" => 16, "大王" => 17
|
|
}).
|
|
|
|
%% 牌型定义
|
|
-record(card_pattern, {
|
|
type, % single | pair | triple | triple_with_one | straight |
|
|
% straight_pair | airplane | airplane_with_wings | four_with_two | bomb | rocket
|
|
value, % 主牌值
|
|
length = 1, % 顺子长度
|
|
extra = [] % 附加牌
|
|
}).
|
|
|
|
%% 验证出牌是否合法
|
|
validate_play(Cards, LastPlay) ->
|
|
case analyze_card_pattern(Cards) of
|
|
{invalid, _} ->
|
|
false;
|
|
{Pattern, Value} ->
|
|
case analyze_card_pattern(LastPlay) of
|
|
{Pattern, LastValue} when Value > LastValue ->
|
|
true;
|
|
{_, _} ->
|
|
check_special_cases(Pattern, Value, LastPlay);
|
|
_ ->
|
|
false
|
|
end
|
|
end.
|
|
|
|
%% 分析牌型
|
|
analyze_card_pattern(Cards) ->
|
|
Grouped = group_cards(Cards),
|
|
case identify_pattern(Grouped) of
|
|
{Type, Value} when Type =/= invalid ->
|
|
{#card_pattern{type = Type, value = Value}, Value};
|
|
_ ->
|
|
{invalid, 0}
|
|
end.
|
|
|
|
%% 计算手牌价值
|
|
calculate_card_value(Cards) ->
|
|
{Pattern, BaseValue} = analyze_card_pattern(Cards),
|
|
case Pattern#card_pattern.type of
|
|
bomb -> BaseValue * 2;
|
|
rocket -> 1000;
|
|
_ -> BaseValue
|
|
end.
|
|
|
|
%% 评估手牌强度
|
|
evaluate_hand_strength(Cards) ->
|
|
Patterns = find_all_possible_patterns(Cards),
|
|
BaseScore = lists:foldl(
|
|
fun(Pattern, Acc) ->
|
|
Acc + calculate_pattern_value(Pattern)
|
|
end,
|
|
0,
|
|
Patterns
|
|
),
|
|
adjust_score_by_combination(BaseScore, Cards).
|
|
|
|
%% 找出所有可能的牌型组合
|
|
find_all_possible_patterns(Cards) ->
|
|
Grouped = group_cards(Cards),
|
|
Singles = find_singles(Grouped),
|
|
Pairs = find_pairs(Grouped),
|
|
Triples = find_triples(Grouped),
|
|
Straights = find_straights(Cards),
|
|
StraightPairs = find_straight_pairs(Grouped),
|
|
Airplanes = find_airplanes(Grouped),
|
|
Bombs = find_bombs(Grouped),
|
|
Rockets = find_rockets(Cards),
|
|
Singles ++ Pairs ++ Triples ++ Straights ++ StraightPairs ++
|
|
Airplanes ++ Bombs ++ Rockets.
|
|
|
|
%% 内部辅助函数
|
|
|
|
group_cards(Cards) ->
|
|
lists:foldl(
|
|
fun({_, Number}, Acc) ->
|
|
maps:update_with(
|
|
Number,
|
|
fun(Count) -> Count + 1 end,
|
|
1,
|
|
Acc
|
|
)
|
|
end,
|
|
#{},
|
|
Cards
|
|
).
|
|
|
|
identify_pattern(Grouped) ->
|
|
case maps:size(Grouped) of
|
|
1 ->
|
|
[{Number, Count}] = maps:to_list(Grouped),
|
|
case Count of
|
|
1 -> {single, maps:get(Number, ?CARD_VALUES)};
|
|
2 -> {pair, maps:get(Number, ?CARD_VALUES)};
|
|
3 -> {triple, maps:get(Number, ?CARD_VALUES)};
|
|
4 -> {bomb, maps:get(Number, ?CARD_VALUES)};
|
|
_ -> {invalid, 0}
|
|
end;
|
|
_ ->
|
|
identify_complex_pattern(Grouped)
|
|
end.
|
|
|
|
identify_complex_pattern(Grouped) ->
|
|
case find_straight_pattern(Grouped) of
|
|
{ok, Pattern} -> Pattern;
|
|
false ->
|
|
case find_airplane_pattern(Grouped) of
|
|
{ok, Pattern} -> Pattern;
|
|
false ->
|
|
case find_four_with_two(Grouped) of
|
|
{ok, Pattern} -> Pattern;
|
|
false -> {invalid, 0}
|
|
end
|
|
end
|
|
end.
|
|
|
|
find_straight_pattern(Grouped) ->
|
|
Numbers = lists:sort(maps:keys(Grouped)),
|
|
case is_consecutive(Numbers) of
|
|
true when length(Numbers) >= 5 ->
|
|
MaxValue = lists:max([maps:get(N, ?CARD_VALUES) || N <- Numbers]),
|
|
{ok, {straight, MaxValue}};
|
|
_ ->
|
|
false
|
|
end.
|
|
|
|
is_consecutive(Numbers) ->
|
|
case Numbers of
|
|
[] -> true;
|
|
[_] -> true;
|
|
[First|Rest] ->
|
|
lists:all(
|
|
fun({A, B}) -> maps:get(B, ?CARD_VALUES) - maps:get(A, ?CARD_VALUES) =:= 1 end,
|
|
lists:zip(Numbers, Rest)
|
|
)
|
|
end.
|
|
|
|
calculate_pattern_value(Pattern) ->
|
|
case Pattern of
|
|
#card_pattern{type = single, value = V} -> V;
|
|
#card_pattern{type = pair, value = V} -> V * 2;
|
|
#card_pattern{type = triple, value = V} -> V * 3;
|
|
#card_pattern{type = bomb, value = V} -> V * 4;
|
|
#card_pattern{type = rocket} -> 1000;
|
|
#card_pattern{type = straight, value = V, length = L} -> V * L;
|
|
_ -> 0
|
|
end.
|
|
|
|
adjust_score_by_combination(BaseScore, Cards) ->
|
|
CombinationBonus = case length(Cards) of
|
|
N when N =< 5 -> BaseScore * 1.2;
|
|
N when N =< 10 -> BaseScore * 1.1;
|
|
_ -> BaseScore
|
|
end,
|
|
round(CombinationBonus).
|
|
|
|
check_special_cases(Pattern, Value, LastPlay) ->
|
|
case Pattern#card_pattern.type of
|
|
bomb -> true;
|
|
rocket -> true;
|
|
_ -> false
|
|
end.
|