diff --git a/src/doudizhu_ai_strategy.erl b/src/doudizhu_ai_strategy.erl index 5255c2f..38a5897 100644 --- a/src/doudizhu_ai_strategy.erl +++ b/src/doudizhu_ai_strategy.erl @@ -8,7 +8,8 @@ analyze_hand_value/1 ]). --include("card_types.hrl"). +-include("../include/card_types.hrl"). +-include("../include/game_records.hrl"). -record(strategy_context, { game_stage, % early_game | mid_game | end_game @@ -68,6 +69,102 @@ execute_strategy(Strategy, GameState, PlayerState) -> %% 内部函数 +%% 获取对手剩余牌数 +get_opponent_cards(GameState) -> + Players = GameState#game_state.players, + lists:foldl(fun({_Pid, Cards, _Role}, Acc) -> + Acc + length(Cards) + end, 0, Players). + +%% 获取最后一手牌 +get_last_play(GameState) -> + case GameState#game_state.last_play of + [] -> none; + {_Pid, Cards} -> Cards + end. + +%% 确定游戏阶段 +determine_game_stage(GameState) -> + % 根据已出牌数量和总牌数判断游戏阶段 + PlayedCards = GameState#game_state.played_cards, + TotalPlayed = lists:foldl(fun({_Pid, Cards}, Acc) -> + Acc + length(Cards) + end, 0, PlayedCards), + + % 假设总牌数为54张 + case TotalPlayed of + N when N < 18 -> early_game; + N when N < 36 -> mid_game; + _ -> end_game + end. + +%% 评估手牌强度 +evaluate_hand_strength(HandCards) -> + HandValue = analyze_hand_value(HandCards), + + % 计算各种牌型的权重得分 + SinglesScore = length(HandValue#hand_value.singles) * 0.1, + PairsScore = length(HandValue#hand_value.pairs) * 0.2, + TriplesScore = length(HandValue#hand_value.triples) * 0.3, + SequencesScore = length(HandValue#hand_value.sequences) * 0.4, + BombsScore = length(HandValue#hand_value.bombs) * 0.6, + RocketsScore = length(HandValue#hand_value.rockets) * 1.0, + + % 归一化到0-1范围 + TotalScore = SinglesScore + PairsScore + TriplesScore + SequencesScore + BombsScore + RocketsScore, + NormalizedScore = min(1.0, TotalScore / 10), + NormalizedScore. + +%% 计算控制水平 +calculate_control_level(GameState, PlayerState) -> + % 根据手牌中的高牌和炸弹数量评估控制能力 + HandCards = PlayerState#state.hand_cards, + HandValue = analyze_hand_value(HandCards), + + % 计算高牌数量(2和大小王) + HighCards = lists:filter(fun({Value, _}) -> + Value >= ?CARD_VALUE_2 + end, HandCards), + + % 计算控制得分 + HighCardScore = length(HighCards) * 0.2, + BombScore = length(HandValue#hand_value.bombs) * 0.4, + RocketScore = length(HandValue#hand_value.rockets) * 0.4, + + % 归一化到0-1范围 + ControlScore = HighCardScore + BombScore + RocketScore, + min(1.0, ControlScore). + +%% 估计胜率 +estimate_winning_probability(GameState, PlayerState) -> + % 简单估计:根据手牌数量和强度 + HandCards = PlayerState#state.hand_cards, + HandStrength = evaluate_hand_strength(HandCards), + CardsRemaining = length(HandCards), + + % 获取对手剩余牌数 + OpponentCards = get_opponent_cards(GameState), + + % 计算牌数优势 + CardAdvantage = case PlayerState#state.role of + dizhu -> + % 地主初始多3张牌,所以需要调整 + case OpponentCards of + 0 -> 1.0; % 对手没牌了,必输 + _ -> max(0, 1.0 - (CardsRemaining / OpponentCards)) + end; + nongmin -> + % 农民需要合作,所以计算方式不同 + case CardsRemaining of + 0 -> 1.0; % 自己没牌了,必赢 + _ -> max(0, 1.0 - (CardsRemaining / max(1, OpponentCards))) + end + end, + + % 综合手牌强度和牌数优势 + WinProb = HandStrength * 0.6 + CardAdvantage * 0.4, + min(1.0, WinProb). + %% 选择基础策略 choose_base_strategy(Context) -> case {Context#strategy_context.game_stage, Context#strategy_context.role} of @@ -263,137 +360,312 @@ find_triples(GroupedCards) -> end end, [], GroupedCards). -find_sequences(GroupedCards) -> - Values = lists:sort(maps:keys(GroupedCards)), - find_consecutive_sequences(Values, GroupedCards, 5). - find_bombs(GroupedCards) -> maps:fold(fun(Value, Cards, Acc) -> case length(Cards) >= 4 of - true -> [{Value, Cards}|Acc]; + true -> [{Value, lists:sublist(Cards, 4)}|Acc]; false -> Acc end end, [], GroupedCards). find_rockets(GroupedCards) -> - case {maps:get(?CARD_JOKER_SMALL, GroupedCards, []), - maps:get(?CARD_JOKER_BIG, GroupedCards, [])} of - {[Small], [Big]} -> [{?CARD_JOKER_BIG, [Small, Big]}]; + SmallJoker = maps:get(?CARD_VALUE_SMALL_JOKER, GroupedCards, []), + BigJoker = maps:get(?CARD_VALUE_BIG_JOKER, GroupedCards, []), + case {SmallJoker, BigJoker} of + {[S|_], [B|_]} -> [{rocket, [S, B]}]; _ -> [] end. -find_consecutive_sequences(Values, GroupedCards, MinLength) -> - find_sequences_of_length(Values, GroupedCards, MinLength, []). +find_sequences(GroupedCards) -> + Values = lists:sort(maps:keys(GroupedCards)), + find_straight_sequences(Values, GroupedCards, 5, []). + +find_straight_sequences([], _, _, Acc) -> Acc; +find_straight_sequences([H|T], GroupedCards, MinLen, Acc) -> + case find_sequence_from(H, T, GroupedCards, MinLen) of + {ok, Seq, Rest} -> + Cards = [hd(maps:get(V, GroupedCards)) || V <- Seq], + find_straight_sequences(Rest, GroupedCards, MinLen, [{H, Cards}|Acc]); + {error, Rest} -> + find_straight_sequences(Rest, GroupedCards, MinLen, Acc) + end. -find_sequences_of_length([], _, _, Acc) -> Acc; -find_sequences_of_length([V|Rest], GroupedCards, MinLength, Acc) -> - case find_sequence_starting_at(V, Rest, GroupedCards, MinLength) of - {ok, Sequence} -> - find_sequences_of_length(Rest, GroupedCards, MinLength, - [Sequence|Acc]); +find_sequence_from(Start, Values, GroupedCards, MinLen) -> + find_sequence_from(Start, Values, GroupedCards, MinLen, [Start], 1). + +find_sequence_from(_, [], _, MinLen, Seq, Len) when Len >= MinLen -> + {ok, lists:reverse(Seq), []}; +find_sequence_from(_, [], _, _, _, _) -> + {error, []}; +find_sequence_from(Prev, [H|T], GroupedCards, MinLen, Seq, Len) -> + case H =:= Prev + 1 of + true -> + find_sequence_from(H, T, GroupedCards, MinLen, [H|Seq], Len+1); + false when Len >= MinLen -> + {ok, lists:reverse(Seq), [H|T]}; false -> - find_sequences_of_length(Rest, GroupedCards, MinLength, Acc) + {error, [H|T]} end. -find_sequence_starting_at(Start, Rest, GroupedCards, MinLength) -> - case collect_sequence(Start, Rest, GroupedCards, []) of - Seq when length(Seq) >= MinLength -> - {ok, {Start, Seq}}; - _ -> - false +%% 查找最佳首出组合 +find_best_lead_combination(HandValue) -> + % 优先级:顺子 > 连对 > 三带一 > 对子 > 单牌 + case HandValue#hand_value.sequences of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case find_straight_pairs(HandValue) of + {ok, Cards} -> {ok, Cards}; + none -> + case find_triple_with_single(HandValue) of + {ok, Cards} -> {ok, Cards}; + none -> + case HandValue#hand_value.pairs of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.singles of + [{_, Card}|_] -> {ok, [Card]}; + [] -> none + end + end + end + end end. -collect_sequence(Current, [Next|Rest], GroupedCards, Acc) when Next =:= Current + 1 -> - case maps:get(Current, GroupedCards) of - Cards when length(Cards) >= 1 -> - collect_sequence(Next, Rest, GroupedCards, [hd(Cards)|Acc]); - _ -> - lists:reverse(Acc) - end; -collect_sequence(Current, _, GroupedCards, Acc) -> - case maps:get(Current, GroupedCards) of - Cards when length(Cards) >= 1 -> - lists:reverse([hd(Cards)|Acc]); - _ -> - lists:reverse(Acc) - end. - -calculate_base_score({Type, Value, _Cards}) -> - BaseScore = Value * 10, - TypeMultiplier = case Type of - ?CARD_TYPE_ROCKET -> 100; - ?CARD_TYPE_BOMB -> 80; - ?CARD_TYPE_STRAIGHT -> 40; - ?CARD_TYPE_STRAIGHT_PAIR -> 35; - ?CARD_TYPE_PLANE -> 30; - ?CARD_TYPE_THREE_TWO -> 25; - ?CARD_TYPE_THREE_ONE -> 20; - ?CARD_TYPE_THREE -> 15; - ?CARD_TYPE_PAIR -> 10; - ?CARD_TYPE_SINGLE -> 5 +%% 查找连对 +find_straight_pairs(HandValue) -> + Pairs = HandValue#hand_value.pairs, + case length(Pairs) >= 3 of + true -> + SortedPairs = lists:sort(fun({V1, _}, {V2, _}) -> V1 =< V2 end, Pairs), + find_consecutive_pairs(SortedPairs, 3); + false -> none + end. + +find_consecutive_pairs(Pairs, MinLen) -> + find_consecutive_pairs(Pairs, MinLen, []). + +find_consecutive_pairs([], _, []) -> none; +find_consecutive_pairs([], MinLen, Acc) when length(Acc) >= MinLen -> + {ok, lists:flatten([Cards || {_, Cards} <- lists:reverse(Acc)])}; +find_consecutive_pairs([], _, _) -> none; +find_consecutive_pairs([{V, Cards}|Rest], MinLen, []) -> + find_consecutive_pairs(Rest, MinLen, [{V, Cards}]); +find_consecutive_pairs([{V, Cards}|Rest], MinLen, [{PrevV, _}|_]=Acc) -> + case V =:= PrevV + 1 of + true -> find_consecutive_pairs(Rest, MinLen, [{V, Cards}|Acc]); + false when length(Acc) >= MinLen -> + {ok, lists:flatten([C || {_, C} <- lists:reverse(Acc)])}; + false -> find_consecutive_pairs(Rest, MinLen, [{V, Cards}]) + end. + +%% 查找三带一 +find_triple_with_single(HandValue) -> + case {HandValue#hand_value.triples, HandValue#hand_value.singles} of + {[{TripleV, TripleCards}|_], [{SingleV, SingleCard}|_]} when TripleV =/= SingleV -> + {ok, TripleCards ++ [SingleCard]}; + _ -> none + end. + +%% 查找单牌 +find_single_card(HandValue) -> + case HandValue#hand_value.singles of + [{_, Card}|_] -> [Card]; + [] -> + case HandValue#hand_value.pairs of + [{_, [Card|_]}|_] -> [Card]; + [] -> + case HandValue#hand_value.triples of + [{_, [Card|_]}|_] -> [Card]; + [] -> [] + end + end + end. + +%% 查找最小单牌 +find_smallest_single(HandValue) -> + AllSingles = case HandValue#hand_value.singles of + [] -> []; + Singles -> [{V, C} || {V, C} <- Singles] end, - BaseScore * TypeMultiplier / 100. + + case AllSingles of + [] -> []; + _ -> + [{_, Card}|_] = lists:sort(fun({V1, _}, {V2, _}) -> V1 =< V2 end, AllSingles), + [Card] + end. + +%% 查找最小炸弹 +find_smallest_bomb(HandValue) -> + case HandValue#hand_value.bombs of + [] -> []; + Bombs -> + [{_, Cards}|_] = lists:sort(fun({V1, _}, {V2, _}) -> V1 =< V2 end, Bombs), + Cards + end. + +%% 查找任意可出牌 +find_any_playable(HandValue) -> + case find_single_card(HandValue) of + [] -> []; + Cards -> Cards + end. + +%% 查找安全首出 +find_safe_lead(HandValue) -> + % 优先出对子和三张 + case HandValue#hand_value.pairs of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.triples of + [{_, Cards}|_] -> {ok, Cards}; + [] -> none + end + end. + +%% 查找安全跟牌 +find_safe_follow(HandValue, LastPlay) -> + % 简单实现:找到比LastPlay大的最小牌 + find_minimum_greater_combination(HandValue, LastPlay). + +%% 查找控制组合 +find_control_combination(HandValue) -> + % 优先出单牌,保留炸弹和对子 + case HandValue#hand_value.singles of + [{_, Card}|_] -> {ok, [Card]}; + [] -> find_safe_lead(HandValue) + end. + +%% 查找节奏出牌 +find_tempo_play(HandValue) -> + % 简单实现:出最小的单牌 + [Card] = find_smallest_single(HandValue), + [Card]. + +%% 查找最小的大于LastPlay的组合 +find_minimum_greater_combination(HandValue, LastPlay) -> + % 简单实现,实际应该根据LastPlay的牌型查找对应的更大牌型 + % 这里假设LastPlay是单牌 + case LastPlay of + [LastCard] -> + {LastValue, _} = LastCard, + GreaterSingles = [{V, C} || {V, C} <- HandValue#hand_value.singles, V > LastValue], + case GreaterSingles of + [] -> none; + _ -> + [{_, Card}|_] = lists:sort(fun({V1, _}, {V2, _}) -> V1 =< V2 end, GreaterSingles), + {ok, [Card]} + end; + _ -> none % 简化处理,实际应该处理各种牌型 + end. + +%% 查找压制性出牌 +find_crushing_play(HandValue, _LastPlay) -> + % 优先使用炸弹 + case HandValue#hand_value.bombs of + [] -> + case HandValue#hand_value.rockets of + [] -> none; + [{_, Cards}|_] -> {ok, Cards} + end; + [{_, Cards}|_] -> {ok, Cards} + end. + +%% 查找最强组合 +find_strongest_combination(HandValue) -> + % 优先级:火箭 > 炸弹 > 顺子 > 三带 > 对子 > 单牌 + case HandValue#hand_value.rockets of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.bombs of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.sequences of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.triples of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.pairs of + [{_, Cards}|_] -> {ok, Cards}; + [] -> + case HandValue#hand_value.singles of + [{_, Card}|_] -> {ok, [Card]}; + [] -> none + end + end + end + end + end + end. +%% 判断是否应该使用炸弹 +should_use_bomb(HandValue, LastPlay) -> + % 简单实现:当剩余牌数少于8张且有炸弹时考虑使用 + length(lists:flatten([C || {_, C} <- HandValue#hand_value.singles] ++ + [C || {_, C} <- HandValue#hand_value.pairs] ++ + [C || {_, C} <- HandValue#hand_value.triples])) < 8 andalso + HandValue#hand_value.bombs =/= []. + +%% 判断是否应该保持控制 +should_maintain_control(HandValue, LastPlay) -> + % 简单实现:当有足够的高牌时尝试保持控制 + HighCards = [{V, C} || {V, C} <- HandValue#hand_value.singles, V >= ?CARD_VALUE_J], + length(HighCards) >= 2. + +%% 计算基础得分 +calculate_base_score(Move) -> + % 简单实现:根据牌型和牌值计算基础得分 + case length(Move) of + 1 -> 0.3; + 2 -> + [{V1, _}, {V2, _}] = Move, + case V1 =:= V2 of + true -> 0.5; % 对子 + false -> 0.4 % 其他两张牌 + end; + 3 -> 0.6; + 4 -> + [{V1, _}, {V2, _}, {V3, _}, {V4, _}] = Move, + case V1 =:= V2 andalso V2 =:= V3 andalso V3 =:= V4 of + true -> 0.9; % 炸弹 + false -> 0.7 % 其他四张牌 + end; + _ -> 0.8 % 长牌型 + end. + +%% 计算节奏得分 calculate_tempo_score(Move, Context) -> + % 简单实现:根据游戏阶段和出牌类型计算节奏得分 case Context#strategy_context.game_stage of - early_game -> calculate_early_tempo(Move, Context); - mid_game -> calculate_mid_tempo(Move, Context); - end_game -> calculate_end_tempo(Move, Context) + early_game -> 0.4; + mid_game -> 0.6; + end_game -> 0.8 end. +%% 计算控制得分 calculate_control_score(Move, Context) -> - BaseControl = case Move of - {Type, _, _} when Type =:= ?CARD_TYPE_BOMB; - Type =:= ?CARD_TYPE_ROCKET -> 1.0; - {_, Value, _} when Value >= ?CARD_2 -> 0.8; - _ -> 0.5 - end, - BaseControl * Context#strategy_context.control_level. + % 简单实现:根据控制水平计算控制得分 + Context#strategy_context.control_level. +%% 计算效率得分 calculate_efficiency_score(Move, Context) -> - {Type, _, Cards} = Move, - CardsUsed = length(Cards), - RemainingCards = Context#strategy_context.cards_remaining - CardsUsed, - Efficiency = CardsUsed / max(1, Context#strategy_context.cards_remaining), - Efficiency * (1 + (20 - RemainingCards) / 20). + % 简单实现:根据出牌数量和剩余牌数计算效率得分 + CardsPlayed = length(Move), + CardsRemaining = Context#strategy_context.cards_remaining, + min(1.0, CardsPlayed / max(1, CardsRemaining) * 3). +%% 根据上下文调整得分 adjust_score_for_context(Score, Move, Context, LastPlay) -> - case {Context#strategy_context.role, LastPlay} of - {dizhu, none} -> Score * 1.2; - {dizhu, _} -> Score * 1.1; - {nongmin, _} -> Score - end. - -calculate_early_tempo(Move, Context) -> - case Move of - {Type, _, _} when Type =:= ?CARD_TYPE_SINGLE; - Type =:= ?CARD_TYPE_PAIR -> - 0.7; - {Type, _, _} when Type =:= ?CARD_TYPE_STRAIGHT; - Type =:= ?CARD_TYPE_STRAIGHT_PAIR -> - 0.9; - _ -> - 0.5 - end. - -calculate_mid_tempo(Move, Context) -> - case Move of - {Type, _, _} when Type =:= ?CARD_TYPE_THREE_ONE; - Type =:= ?CARD_TYPE_THREE_TWO -> - 0.8; - {Type, _, _} when Type =:= ?CARD_TYPE_PLANE -> - 0.9; - _ -> - 0.6 - end. - -calculate_end_tempo(Move, Context) -> - case Move of - {Type, _, _} when Type =:= ?CARD_TYPE_BOMB; - Type =:= ?CARD_TYPE_ROCKET -> - 1.0; - {_, Value, _} when Value >= ?CARD_2 -> - 0.9; - _ -> - 0.7 + % 简单实现:根据游戏阶段和角色调整得分 + AdjustedScore = case Context#strategy_context.role of + dizhu -> Score * 1.1; + nongmin -> Score * 0.9 + end, + + % 根据游戏阶段进一步调整 + case Context#strategy_context.game_stage of + early_game -> AdjustedScore * 0.9; + mid_game -> AdjustedScore; + end_game -> AdjustedScore * 1.1 end. \ No newline at end of file