|
@ -10,6 +10,7 @@ |
|
|
]). |
|
|
]). |
|
|
|
|
|
|
|
|
-include("card_types.hrl"). |
|
|
-include("card_types.hrl"). |
|
|
|
|
|
-import(game_logic, [validate_play/2, analyze_card_pattern/1, calculate_card_value/1, evaluate_hand_strength/1, find_singles/1]). |
|
|
|
|
|
|
|
|
-record(ai_state, { |
|
|
-record(ai_state, { |
|
|
role, % dizhu | nongmin |
|
|
role, % dizhu | nongmin |
|
@ -65,7 +66,7 @@ calculate_win_rate(AIState) -> |
|
|
HandStrength = evaluate_hand(AIState#ai_state.hand_cards), |
|
|
HandStrength = evaluate_hand(AIState#ai_state.hand_cards), |
|
|
PositionValue = evaluate_position(AIState), |
|
|
PositionValue = evaluate_position(AIState), |
|
|
ControlValue = evaluate_control(AIState), |
|
|
ControlValue = evaluate_control(AIState), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
calculate_probability(HandStrength, PositionValue, ControlValue). |
|
|
calculate_probability(HandStrength, PositionValue, ControlValue). |
|
|
|
|
|
|
|
|
%% 内部函数 |
|
|
%% 内部函数 |
|
@ -76,7 +77,7 @@ analyze_context(AIState, GameState) -> |
|
|
CardsRemaining = length(AIState#ai_state.hand_cards), |
|
|
CardsRemaining = length(AIState#ai_state.hand_cards), |
|
|
GreaterCards = count_greater_cards(AIState#ai_state.hand_cards, LastPlay), |
|
|
GreaterCards = count_greater_cards(AIState#ai_state.hand_cards, LastPlay), |
|
|
ControlFactor = calculate_control_factor(AIState, GameState), |
|
|
ControlFactor = calculate_control_factor(AIState, GameState), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#play_context{ |
|
|
#play_context{ |
|
|
last_play = LastPlay, |
|
|
last_play = LastPlay, |
|
|
cards_remaining = CardsRemaining, |
|
|
cards_remaining = CardsRemaining, |
|
@ -101,7 +102,7 @@ should_play(Context, AIState) -> |
|
|
select_best_play(Context, AIState) -> |
|
|
select_best_play(Context, AIState) -> |
|
|
Candidates = generate_candidates(AIState#ai_state.hand_cards, Context), |
|
|
Candidates = generate_candidates(AIState#ai_state.hand_cards, Context), |
|
|
ScoredPlays = [ |
|
|
ScoredPlays = [ |
|
|
{score_play(Play, Context, AIState), Play} |
|
|
|
|
|
|
|
|
{score_play(Play, Context, AIState), Play} |
|
|
|| Play <- Candidates |
|
|
|| Play <- Candidates |
|
|
], |
|
|
], |
|
|
select_highest_scored_play(ScoredPlays, AIState). |
|
|
select_highest_scored_play(ScoredPlays, AIState). |
|
@ -124,7 +125,7 @@ calculate_hand_value(Components) -> |
|
|
TriplesValue = calculate_triples_value(maps:get(triples, Components, [])), |
|
|
TriplesValue = calculate_triples_value(maps:get(triples, Components, [])), |
|
|
SequencesValue = calculate_sequences_value(maps:get(sequences, Components, [])), |
|
|
SequencesValue = calculate_sequences_value(maps:get(sequences, Components, [])), |
|
|
BombsValue = calculate_bombs_value(maps:get(bombs, Components, [])), |
|
|
BombsValue = calculate_bombs_value(maps:get(bombs, Components, [])), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SinglesValue + PairsValue + TriplesValue + SequencesValue + BombsValue. |
|
|
SinglesValue + PairsValue + TriplesValue + SequencesValue + BombsValue. |
|
|
|
|
|
|
|
|
%% 生成有效出牌选择 |
|
|
%% 生成有效出牌选择 |
|
@ -163,16 +164,16 @@ calculate_control_factor(AIState, GameState) -> |
|
|
ControlCards = count_control_cards(AIState#ai_state.hand_cards), |
|
|
ControlCards = count_control_cards(AIState#ai_state.hand_cards), |
|
|
TotalCards = count_total_remaining_cards(GameState), |
|
|
TotalCards = count_total_remaining_cards(GameState), |
|
|
RemainingCards = length(AIState#ai_state.hand_cards), |
|
|
RemainingCards = length(AIState#ai_state.hand_cards), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ControlRatio = ControlCards / max(1, RemainingCards), |
|
|
ControlRatio = ControlCards / max(1, RemainingCards), |
|
|
PositionBonus = calculate_position_bonus(AIState, GameState), |
|
|
PositionBonus = calculate_position_bonus(AIState, GameState), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ControlRatio * PositionBonus. |
|
|
ControlRatio * PositionBonus. |
|
|
|
|
|
|
|
|
%% 判断是否必须出牌 |
|
|
%% 判断是否必须出牌 |
|
|
must_play(AIState, GameState) -> |
|
|
must_play(AIState, GameState) -> |
|
|
is_current_player(AIState, GameState) andalso |
|
|
is_current_player(AIState, GameState) andalso |
|
|
not has_active_play(GameState). |
|
|
|
|
|
|
|
|
not has_active_play(GameState). |
|
|
|
|
|
|
|
|
%% 评估是否应该大过上家 |
|
|
%% 评估是否应该大过上家 |
|
|
should_beat_last_play(Context, AIState) -> |
|
|
should_beat_last_play(Context, AIState) -> |
|
@ -182,7 +183,7 @@ should_beat_last_play(Context, AIState) -> |
|
|
HandStrength = evaluate_hand(AIState#ai_state.hand_cards), |
|
|
HandStrength = evaluate_hand(AIState#ai_state.hand_cards), |
|
|
ControlLevel = Context#play_context.control_factor, |
|
|
ControlLevel = Context#play_context.control_factor, |
|
|
CardsLeft = Context#play_context.cards_remaining, |
|
|
CardsLeft = Context#play_context.cards_remaining, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
should_beat(HandStrength, ControlLevel, CardsLeft, LastPlay) |
|
|
should_beat(HandStrength, ControlLevel, CardsLeft, LastPlay) |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
@ -192,7 +193,7 @@ generate_candidates(Cards, Context) -> |
|
|
none -> generate_leading_plays(Cards); |
|
|
none -> generate_leading_plays(Cards); |
|
|
LastPlay -> generate_following_plays(Cards, LastPlay) |
|
|
LastPlay -> generate_following_plays(Cards, LastPlay) |
|
|
end, |
|
|
end, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
filter_candidates(BasePlays, Context). |
|
|
filter_candidates(BasePlays, Context). |
|
|
|
|
|
|
|
|
%% 对出牌进行评分 |
|
|
%% 对出牌进行评分 |
|
@ -201,18 +202,18 @@ score_play(Play, Context, AIState) -> |
|
|
TempoScore = calculate_tempo_score(Play, Context), |
|
|
TempoScore = calculate_tempo_score(Play, Context), |
|
|
ControlScore = calculate_control_score(Play, Context, AIState), |
|
|
ControlScore = calculate_control_score(Play, Context, AIState), |
|
|
EfficiencyScore = calculate_efficiency_score(Play, Context), |
|
|
EfficiencyScore = calculate_efficiency_score(Play, Context), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FinalScore = BaseScore * 0.4 + |
|
|
FinalScore = BaseScore * 0.4 + |
|
|
TempoScore * 0.2 + |
|
|
|
|
|
ControlScore * 0.3 + |
|
|
|
|
|
EfficiencyScore * 0.1, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TempoScore * 0.2 + |
|
|
|
|
|
ControlScore * 0.3 + |
|
|
|
|
|
EfficiencyScore * 0.1, |
|
|
|
|
|
|
|
|
adjust_score_for_context(FinalScore, Play, Context, AIState). |
|
|
adjust_score_for_context(FinalScore, Play, Context, AIState). |
|
|
|
|
|
|
|
|
%% 选择得分最高的出牌 |
|
|
%% 选择得分最高的出牌 |
|
|
select_highest_scored_play(ScoredPlays, AIState) -> |
|
|
select_highest_scored_play(ScoredPlays, AIState) -> |
|
|
case lists:sort(fun({Score1, _}, {Score2, _}) -> |
|
|
|
|
|
Score1 >= Score2 |
|
|
|
|
|
|
|
|
case lists:sort(fun({Score1, _}, {Score2, _}) -> |
|
|
|
|
|
Score1 >= Score2 |
|
|
end, ScoredPlays) of |
|
|
end, ScoredPlays) of |
|
|
[{Score, Play}|_] when Score > 0 -> |
|
|
[{Score, Play}|_] when Score > 0 -> |
|
|
{play, Play, update_after_play(Play, AIState)}; |
|
|
{play, Play, update_after_play(Play, AIState)}; |
|
@ -224,7 +225,7 @@ select_highest_scored_play(ScoredPlays, AIState) -> |
|
|
calculate_singles_value(Singles) -> |
|
|
calculate_singles_value(Singles) -> |
|
|
lists:sum([ |
|
|
lists:sum([ |
|
|
case Value of |
|
|
case Value of |
|
|
V when V >= ?CARD_2 -> Value * 1.5; |
|
|
|
|
|
|
|
|
V when V >= ?CARD_VALUE_2 -> Value * 1.5; |
|
|
_ -> Value |
|
|
_ -> Value |
|
|
end || {Value, _} <- Singles |
|
|
end || {Value, _} <- Singles |
|
|
]). |
|
|
]). |
|
@ -240,7 +241,7 @@ calculate_triples_value(Triples) -> |
|
|
%% 计算顺子价值 |
|
|
%% 计算顺子价值 |
|
|
calculate_sequences_value(Sequences) -> |
|
|
calculate_sequences_value(Sequences) -> |
|
|
lists:sum([ |
|
|
lists:sum([ |
|
|
Value * length(Cards) * 1.8 |
|
|
|
|
|
|
|
|
Value * length(Cards) * 1.8 |
|
|
|| {Value, Cards} <- Sequences |
|
|
|| {Value, Cards} <- Sequences |
|
|
]). |
|
|
]). |
|
|
|
|
|
|
|
@ -256,7 +257,7 @@ generate_leading_plays(Cards) -> |
|
|
Triples = generate_triple_plays(Components), |
|
|
Triples = generate_triple_plays(Components), |
|
|
Sequences = generate_sequence_plays(Components), |
|
|
Sequences = generate_sequence_plays(Components), |
|
|
Bombs = generate_bomb_plays(Components), |
|
|
Bombs = generate_bomb_plays(Components), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Singles ++ Pairs ++ Triples ++ Sequences ++ Bombs. |
|
|
Singles ++ Pairs ++ Triples ++ Sequences ++ Bombs. |
|
|
|
|
|
|
|
|
%% 生成跟牌选择 |
|
|
%% 生成跟牌选择 |
|
@ -264,7 +265,7 @@ generate_following_plays(Cards, {Type, Value, _} = LastPlay) -> |
|
|
ValidPlays = find_greater_plays(Cards, Type, Value), |
|
|
ValidPlays = find_greater_plays(Cards, Type, Value), |
|
|
BombPlays = find_bomb_plays(Cards), |
|
|
BombPlays = find_bomb_plays(Cards), |
|
|
RocketPlay = find_rocket_play(Cards), |
|
|
RocketPlay = find_rocket_play(Cards), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ValidPlays ++ BombPlays ++ RocketPlay. |
|
|
ValidPlays ++ BombPlays ++ RocketPlay. |
|
|
|
|
|
|
|
|
%% 计算基础分数 |
|
|
%% 计算基础分数 |
|
@ -298,7 +299,7 @@ calculate_control_score(Play, Context, AIState) -> |
|
|
BaseControl = case Type of |
|
|
BaseControl = case Type of |
|
|
?CARD_TYPE_BOMB -> 1.0; |
|
|
?CARD_TYPE_BOMB -> 1.0; |
|
|
?CARD_TYPE_ROCKET -> 1.0; |
|
|
?CARD_TYPE_ROCKET -> 1.0; |
|
|
_ when Value >= ?CARD_2 -> 0.8; |
|
|
|
|
|
|
|
|
_ when Value >= ?CARD_VALUE_2 -> 0.8; |
|
|
_ -> 0.5 |
|
|
_ -> 0.5 |
|
|
end, |
|
|
end, |
|
|
BaseControl * Context#play_context.control_factor. |
|
|
BaseControl * Context#play_context.control_factor. |
|
@ -317,13 +318,13 @@ adjust_score_for_context(Score, Play, Context, AIState) -> |
|
|
dizhu -> 1.2; |
|
|
dizhu -> 1.2; |
|
|
nongmin -> 1.0 |
|
|
nongmin -> 1.0 |
|
|
end, |
|
|
end, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
StageMultiplier = case Context#play_context.game_stage of |
|
|
StageMultiplier = case Context#play_context.game_stage of |
|
|
early_game -> 0.9; |
|
|
early_game -> 0.9; |
|
|
mid_game -> 1.0; |
|
|
mid_game -> 1.0; |
|
|
end_game -> 1.1 |
|
|
end_game -> 1.1 |
|
|
end, |
|
|
end, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Score * RoleMultiplier * StageMultiplier. |
|
|
Score * RoleMultiplier * StageMultiplier. |
|
|
|
|
|
|
|
|
%% 更新出牌后的状态 |
|
|
%% 更新出牌后的状态 |
|
@ -331,7 +332,7 @@ update_after_play(Play, AIState) -> |
|
|
{_, _, Cards} = Play, |
|
|
{_, _, Cards} = Play, |
|
|
NewHand = AIState#ai_state.hand_cards -- Cards, |
|
|
NewHand = AIState#ai_state.hand_cards -- Cards, |
|
|
NewPlayed = AIState#ai_state.played_cards ++ Cards, |
|
|
NewPlayed = AIState#ai_state.played_cards ++ Cards, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AIState#ai_state{ |
|
|
AIState#ai_state{ |
|
|
hand_cards = NewHand, |
|
|
hand_cards = NewHand, |
|
|
played_cards = NewPlayed |
|
|
played_cards = NewPlayed |
|
@ -343,7 +344,7 @@ normalize_probability(P) -> |
|
|
max(0.0, min(1.0, P)). |
|
|
max(0.0, min(1.0, P)). |
|
|
|
|
|
|
|
|
count_control_cards(Cards) -> |
|
|
count_control_cards(Cards) -> |
|
|
length([C || {V, _} = C <- Cards, V >= ?CARD_2]). |
|
|
|
|
|
|
|
|
length([C || {V, _} = C <- Cards, V >= ?CARD_VALUE_2]). |
|
|
|
|
|
|
|
|
calculate_position_bonus(AIState, GameState) -> |
|
|
calculate_position_bonus(AIState, GameState) -> |
|
|
case get_position(AIState, GameState) of |
|
|
case get_position(AIState, GameState) of |
|
@ -369,7 +370,7 @@ should_beat(HandStrength, ControlLevel, CardsLeft, LastPlay) -> |
|
|
StrengthFactor = HandStrength / 100, |
|
|
StrengthFactor = HandStrength / 100, |
|
|
ControlFactor = ControlLevel / 100, |
|
|
ControlFactor = ControlLevel / 100, |
|
|
CardsFactor = (20 - CardsLeft) / 20, |
|
|
CardsFactor = (20 - CardsLeft) / 20, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PlayThreshold = BaseThreshold * (StrengthFactor + ControlFactor + CardsFactor) / 3, |
|
|
PlayThreshold = BaseThreshold * (StrengthFactor + ControlFactor + CardsFactor) / 3, |
|
|
evaluate_play_value(LastPlay) < PlayThreshold. |
|
|
evaluate_play_value(LastPlay) < PlayThreshold. |
|
|
|
|
|
|
|
@ -386,44 +387,44 @@ evaluate_play_value({Type, Value, Cards}) -> |
|
|
?CARD_TYPE_PAIR -> 0.2; |
|
|
?CARD_TYPE_PAIR -> 0.2; |
|
|
?CARD_TYPE_SINGLE -> 0.1 |
|
|
?CARD_TYPE_SINGLE -> 0.1 |
|
|
end, |
|
|
end, |
|
|
|
|
|
|
|
|
ValueBonus = Value / ?CARD_JOKER_BIG, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ValueBonus = Value / ?CARD_VALUE_BIG_JOKER, |
|
|
CardCountFactor = length(Cards) / 10, |
|
|
CardCountFactor = length(Cards) / 10, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BaseValue * (1 + ValueBonus) * (1 + CardCountFactor). |
|
|
BaseValue * (1 + ValueBonus) * (1 + CardCountFactor). |
|
|
|
|
|
|
|
|
%% 牌型生成函数 |
|
|
%% 牌型生成函数 |
|
|
generate_single_plays(Components) -> |
|
|
generate_single_plays(Components) -> |
|
|
[{?CARD_TYPE_SINGLE, Value, [Card]} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_SINGLE, Value, [Card]} || |
|
|
{Value, Card} <- maps:get(singles, Components, [])]. |
|
|
{Value, Card} <- maps:get(singles, Components, [])]. |
|
|
|
|
|
|
|
|
generate_pair_plays(Components) -> |
|
|
generate_pair_plays(Components) -> |
|
|
[{?CARD_TYPE_PAIR, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_PAIR, Value, Cards} || |
|
|
{Value, Cards} <- maps:get(pairs, Components, [])]. |
|
|
{Value, Cards} <- maps:get(pairs, Components, [])]. |
|
|
|
|
|
|
|
|
generate_triple_plays(Components) -> |
|
|
generate_triple_plays(Components) -> |
|
|
Triples = maps:get(triples, Components, []), |
|
|
Triples = maps:get(triples, Components, []), |
|
|
BasicTriples = [{?CARD_TYPE_THREE, Value, Cards} || |
|
|
|
|
|
{Value, Cards} <- Triples], |
|
|
|
|
|
|
|
|
BasicTriples = [{?CARD_TYPE_THREE, Value, Cards} || |
|
|
|
|
|
{Value, Cards} <- Triples], |
|
|
% 生成三带一和三带二 |
|
|
% 生成三带一和三带二 |
|
|
generate_triple_combinations(Triples, Components). |
|
|
generate_triple_combinations(Triples, Components). |
|
|
|
|
|
|
|
|
generate_sequence_plays(Components) -> |
|
|
generate_sequence_plays(Components) -> |
|
|
Sequences = maps:get(sequences, Components, []), |
|
|
Sequences = maps:get(sequences, Components, []), |
|
|
[{?CARD_TYPE_STRAIGHT, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_STRAIGHT, Value, Cards} || |
|
|
{Value, Cards} <- Sequences]. |
|
|
{Value, Cards} <- Sequences]. |
|
|
|
|
|
|
|
|
generate_bomb_plays(Components) -> |
|
|
generate_bomb_plays(Components) -> |
|
|
[{?CARD_TYPE_BOMB, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_BOMB, Value, Cards} || |
|
|
{Value, Cards} <- maps:get(bombs, Components, [])]. |
|
|
{Value, Cards} <- maps:get(bombs, Components, [])]. |
|
|
|
|
|
|
|
|
generate_triple_combinations(Triples, Components) -> |
|
|
generate_triple_combinations(Triples, Components) -> |
|
|
Singles = maps:get(singles, Components, []), |
|
|
Singles = maps:get(singles, Components, []), |
|
|
Pairs = maps:get(pairs, Components, []), |
|
|
Pairs = maps:get(pairs, Components, []), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ThreeOne = generate_three_one(Triples, Singles), |
|
|
ThreeOne = generate_three_one(Triples, Singles), |
|
|
ThreeTwo = generate_three_two(Triples, Pairs), |
|
|
ThreeTwo = generate_three_two(Triples, Pairs), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ThreeOne ++ ThreeTwo. |
|
|
ThreeOne ++ ThreeTwo. |
|
|
|
|
|
|
|
|
generate_three_one(Triples, Singles) -> |
|
|
generate_three_one(Triples, Singles) -> |
|
@ -442,7 +443,7 @@ generate_three_two(Triples, Pairs) -> |
|
|
find_greater_plays(Cards, Type, MinValue) -> |
|
|
find_greater_plays(Cards, Type, MinValue) -> |
|
|
Components = analyze_components(Cards), |
|
|
Components = analyze_components(Cards), |
|
|
case Type of |
|
|
case Type of |
|
|
?CARD_TYPE_SINGLE -> |
|
|
|
|
|
|
|
|
?CARD_TYPE_SINGLE -> |
|
|
find_greater_singles(Components, MinValue); |
|
|
find_greater_singles(Components, MinValue); |
|
|
?CARD_TYPE_PAIR -> |
|
|
?CARD_TYPE_PAIR -> |
|
|
find_greater_pairs(Components, MinValue); |
|
|
find_greater_pairs(Components, MinValue); |
|
@ -464,58 +465,58 @@ find_greater_plays(Cards, Type, MinValue) -> |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
find_greater_singles(Components, MinValue) -> |
|
|
find_greater_singles(Components, MinValue) -> |
|
|
[{?CARD_TYPE_SINGLE, Value, [Card]} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_SINGLE, Value, [Card]} || |
|
|
{Value, Card} <- maps:get(singles, Components, []), |
|
|
{Value, Card} <- maps:get(singles, Components, []), |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_greater_pairs(Components, MinValue) -> |
|
|
find_greater_pairs(Components, MinValue) -> |
|
|
[{?CARD_TYPE_PAIR, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_PAIR, Value, Cards} || |
|
|
{Value, Cards} <- maps:get(pairs, Components, []), |
|
|
{Value, Cards} <- maps:get(pairs, Components, []), |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_greater_triples(Components, MinValue) -> |
|
|
find_greater_triples(Components, MinValue) -> |
|
|
[{?CARD_TYPE_THREE, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_THREE, Value, Cards} || |
|
|
{Value, Cards} <- maps:get(triples, Components, []), |
|
|
{Value, Cards} <- maps:get(triples, Components, []), |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_greater_three_one(Components, MinValue) -> |
|
|
find_greater_three_one(Components, MinValue) -> |
|
|
Triples = [{V, C} || {V, C} <- maps:get(triples, Components, []), |
|
|
Triples = [{V, C} || {V, C} <- maps:get(triples, Components, []), |
|
|
V > MinValue], |
|
|
|
|
|
|
|
|
V > MinValue], |
|
|
Singles = maps:get(singles, Components, []), |
|
|
Singles = maps:get(singles, Components, []), |
|
|
generate_three_one(Triples, Singles). |
|
|
generate_three_one(Triples, Singles). |
|
|
|
|
|
|
|
|
find_greater_three_two(Components, MinValue) -> |
|
|
find_greater_three_two(Components, MinValue) -> |
|
|
Triples = [{V, C} || {V, C} <- maps:get(triples, Components, []), |
|
|
Triples = [{V, C} || {V, C} <- maps:get(triples, Components, []), |
|
|
V > MinValue], |
|
|
|
|
|
|
|
|
V > MinValue], |
|
|
Pairs = maps:get(pairs, Components, []), |
|
|
Pairs = maps:get(pairs, Components, []), |
|
|
generate_three_two(Triples, Pairs). |
|
|
generate_three_two(Triples, Pairs). |
|
|
|
|
|
|
|
|
find_greater_straight(Components, MinValue) -> |
|
|
find_greater_straight(Components, MinValue) -> |
|
|
Sequences = maps:get(sequences, Components, []), |
|
|
Sequences = maps:get(sequences, Components, []), |
|
|
[{?CARD_TYPE_STRAIGHT, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_STRAIGHT, Value, Cards} || |
|
|
{Value, Cards} <- Sequences, |
|
|
{Value, Cards} <- Sequences, |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_greater_straight_pair(Components, MinValue) -> |
|
|
find_greater_straight_pair(Components, MinValue) -> |
|
|
Sequences = find_pair_sequences(Components), |
|
|
Sequences = find_pair_sequences(Components), |
|
|
[{?CARD_TYPE_STRAIGHT_PAIR, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_STRAIGHT_PAIR, Value, Cards} || |
|
|
{Value, Cards} <- Sequences, |
|
|
{Value, Cards} <- Sequences, |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_greater_plane(Components, MinValue) -> |
|
|
find_greater_plane(Components, MinValue) -> |
|
|
Planes = find_planes(Components), |
|
|
Planes = find_planes(Components), |
|
|
[{?CARD_TYPE_PLANE, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_PLANE, Value, Cards} || |
|
|
{Value, Cards} <- Planes, |
|
|
{Value, Cards} <- Planes, |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_greater_bomb(Components, MinValue) -> |
|
|
find_greater_bomb(Components, MinValue) -> |
|
|
[{?CARD_TYPE_BOMB, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_BOMB, Value, Cards} || |
|
|
{Value, Cards} <- maps:get(bombs, Components, []), |
|
|
{Value, Cards} <- maps:get(bombs, Components, []), |
|
|
Value > MinValue]. |
|
|
Value > MinValue]. |
|
|
|
|
|
|
|
|
find_bomb_plays(Cards) -> |
|
|
find_bomb_plays(Cards) -> |
|
|
Components = analyze_components(Cards), |
|
|
Components = analyze_components(Cards), |
|
|
[{?CARD_TYPE_BOMB, Value, Cards} || |
|
|
|
|
|
|
|
|
[{?CARD_TYPE_BOMB, Value, Cards} || |
|
|
{Value, Cards} <- maps:get(bombs, Components, [])]. |
|
|
{Value, Cards} <- maps:get(bombs, Components, [])]. |
|
|
|
|
|
|
|
|
find_rocket_play(Cards) -> |
|
|
find_rocket_play(Cards) -> |
|
@ -526,10 +527,10 @@ find_rocket_play(Cards) -> |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
find_rocket(Components) -> |
|
|
find_rocket(Components) -> |
|
|
case {find_card(?CARD_JOKER_SMALL, Components), |
|
|
|
|
|
find_card(?CARD_JOKER_BIG, Components)} of |
|
|
|
|
|
|
|
|
case {find_card(?CARD_VALUE_SMALL_JOKER, Components), |
|
|
|
|
|
find_card(?CARD_VALUE_BIG_JOKER, Components)} of |
|
|
{{ok, Small}, {ok, Big}} -> |
|
|
{{ok, Small}, {ok, Big}} -> |
|
|
{ok, {?CARD_TYPE_ROCKET, ?CARD_JOKER_BIG, [Small, Big]}}; |
|
|
|
|
|
|
|
|
{ok, {?CARD_TYPE_ROCKET, ?CARD_VALUE_BIG_JOKER, [Small, Big]}}; |
|
|
_ -> |
|
|
_ -> |
|
|
false |
|
|
false |
|
|
end. |
|
|
end. |
|
@ -564,7 +565,7 @@ find_consecutive_pair_sequence(Value, Cards, Rest) -> |
|
|
find_consecutive_pair_sequence(Value, _, [], Acc) -> |
|
|
find_consecutive_pair_sequence(Value, _, [], Acc) -> |
|
|
{lists:flatten(lists:reverse(Acc)), []}; |
|
|
{lists:flatten(lists:reverse(Acc)), []}; |
|
|
find_consecutive_pair_sequence(Value, _, [{NextValue, NextCards} | Rest], Acc) |
|
|
find_consecutive_pair_sequence(Value, _, [{NextValue, NextCards} | Rest], Acc) |
|
|
when NextValue =:= Value + 1 -> |
|
|
|
|
|
|
|
|
when NextValue =:= Value + 1 -> |
|
|
find_consecutive_pair_sequence(NextValue, NextCards, Rest, [NextCards | Acc]); |
|
|
find_consecutive_pair_sequence(NextValue, NextCards, Rest, [NextCards | Acc]); |
|
|
find_consecutive_pair_sequence(_, _, Rest, Acc) -> |
|
|
find_consecutive_pair_sequence(_, _, Rest, Acc) -> |
|
|
{lists:flatten(lists:reverse(Acc)), Rest}. |
|
|
{lists:flatten(lists:reverse(Acc)), Rest}. |
|
@ -584,7 +585,7 @@ find_consecutive_triple_sequence(Value, Cards, Rest) -> |
|
|
find_consecutive_triple_sequence(Value, _, [], Acc) -> |
|
|
find_consecutive_triple_sequence(Value, _, [], Acc) -> |
|
|
{lists:flatten(lists:reverse(Acc)), []}; |
|
|
{lists:flatten(lists:reverse(Acc)), []}; |
|
|
find_consecutive_triple_sequence(Value, _, [{NextValue, NextCards} | Rest], Acc) |
|
|
find_consecutive_triple_sequence(Value, _, [{NextValue, NextCards} | Rest], Acc) |
|
|
when NextValue =:= Value + 1 -> |
|
|
|
|
|
|
|
|
when NextValue =:= Value + 1 -> |
|
|
find_consecutive_triple_sequence(NextValue, NextCards, Rest, [NextCards | Acc]); |
|
|
find_consecutive_triple_sequence(NextValue, NextCards, Rest, [NextCards | Acc]); |
|
|
find_consecutive_triple_sequence(_, _, Rest, Acc) -> |
|
|
find_consecutive_triple_sequence(_, _, Rest, Acc) -> |
|
|
{lists:flatten(lists:reverse(Acc)), Rest}. |
|
|
{lists:flatten(lists:reverse(Acc)), Rest}. |
|
@ -592,8 +593,8 @@ find_consecutive_triple_sequence(_, _, Rest, Acc) -> |
|
|
%% 计算早期、中期和末期的节奏分数 |
|
|
%% 计算早期、中期和末期的节奏分数 |
|
|
calculate_early_tempo({Type, Value, _}, _Context) -> |
|
|
calculate_early_tempo({Type, Value, _}, _Context) -> |
|
|
case Type of |
|
|
case Type of |
|
|
?CARD_TYPE_SINGLE when Value < ?CARD_2 -> 0.8; |
|
|
|
|
|
?CARD_TYPE_PAIR when Value < ?CARD_2 -> 0.7; |
|
|
|
|
|
|
|
|
?CARD_TYPE_SINGLE when Value < ?CARD_VALUE_2 -> 0.8; |
|
|
|
|
|
?CARD_TYPE_PAIR when Value < ?CARD_VALUE_2 -> 0.7; |
|
|
?CARD_TYPE_STRAIGHT -> 0.9; |
|
|
?CARD_TYPE_STRAIGHT -> 0.9; |
|
|
?CARD_TYPE_STRAIGHT_PAIR -> 0.85; |
|
|
?CARD_TYPE_STRAIGHT_PAIR -> 0.85; |
|
|
_ -> 0.5 |
|
|
_ -> 0.5 |
|
@ -631,7 +632,7 @@ filter_candidates(Plays, Context) -> |
|
|
filter_early_game_plays(Plays, _Context) -> |
|
|
filter_early_game_plays(Plays, _Context) -> |
|
|
% 早期游戏倾向于出小牌,保留炸弹 |
|
|
% 早期游戏倾向于出小牌,保留炸弹 |
|
|
[Play || {Type, Value, _} = Play <- Plays, |
|
|
[Play || {Type, Value, _} = Play <- Plays, |
|
|
Type =/= ?CARD_TYPE_BOMB orelse Value >= ?CARD_2]. |
|
|
|
|
|
|
|
|
Type =/= ?CARD_TYPE_BOMB orelse Value >= ?CARD_VALUE_2]. |
|
|
|
|
|
|
|
|
filter_mid_game_plays(Plays, Context) -> |
|
|
filter_mid_game_plays(Plays, Context) -> |
|
|
% 中期游戏根据局势决定是否使用炸弹 |
|
|
% 中期游戏根据局势决定是否使用炸弹 |
|
@ -639,7 +640,7 @@ filter_mid_game_plays(Plays, Context) -> |
|
|
true -> Plays; |
|
|
true -> Plays; |
|
|
false -> |
|
|
false -> |
|
|
[Play || {Type, _, _} = Play <- Plays, |
|
|
[Play || {Type, _, _} = Play <- Plays, |
|
|
Type =/= ?CARD_TYPE_BOMB] |
|
|
|
|
|
|
|
|
Type =/= ?CARD_TYPE_BOMB] |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
filter_end_game_plays(Plays, _Context) -> |
|
|
filter_end_game_plays(Plays, _Context) -> |
|
@ -650,7 +651,7 @@ filter_end_game_plays(Plays, _Context) -> |
|
|
group_cards_by_value(Cards) -> |
|
|
group_cards_by_value(Cards) -> |
|
|
lists:foldl(fun(Card, Acc) -> |
|
|
lists:foldl(fun(Card, Acc) -> |
|
|
{Value, _} = Card, |
|
|
{Value, _} = Card, |
|
|
maps:update_with(Value, |
|
|
|
|
|
|
|
|
maps:update_with(Value, |
|
|
fun(List) -> [Card|List] end, |
|
|
fun(List) -> [Card|List] end, |
|
|
[Card], |
|
|
[Card], |
|
|
Acc) |
|
|
Acc) |