-module(game_core).
|
|
-export([start_game/3, play_cards/3, get_game_state/1, is_valid_play/2]).
|
|
-export([init_deck/0, deal_cards/1, get_card_type/1, compare_cards/2]).
|
|
|
|
-record(game_state, {
|
|
players = [], % [{Pid, Cards, Role}]
|
|
current_player, % Pid
|
|
last_play = [], % {Pid, Cards}
|
|
played_cards = [], % [{Pid, Cards}]
|
|
stage = waiting, % waiting | playing | finished
|
|
landlord_cards = [] % 地主牌
|
|
}).
|
|
|
|
%% 初始化扑克牌
|
|
init_deck() ->
|
|
Colors = ["♠", "♥", "♣", "♦"],
|
|
Numbers = ["3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"],
|
|
Cards = [{Color, Number} || Color <- Colors, Number <- Numbers],
|
|
Jokers = [{"", "小王"}, {"", "大王"}],
|
|
shuffle(Cards ++ Jokers).
|
|
|
|
%% 发牌
|
|
deal_cards(Deck) ->
|
|
{Players, Landlord} = lists:split(51, Deck),
|
|
{lists:sublist(Players, 1, 17),
|
|
lists:sublist(Players, 18, 17),
|
|
lists:sublist(Players, 35, 17),
|
|
Landlord}.
|
|
|
|
%% 开始游戏
|
|
start_game(Player1, Player2, Player3) ->
|
|
Deck = init_deck(),
|
|
{Cards1, Cards2, Cards3, LandlordCards} = deal_cards(Deck),
|
|
% 随机选择地主
|
|
LandlordIdx = rand:uniform(3),
|
|
Players = assign_roles([{Player1, Cards1}, {Player2, Cards2}, {Player3, Cards3}], LandlordIdx),
|
|
#game_state{
|
|
players = Players,
|
|
current_player = element(1, lists:nth(LandlordIdx, Players)),
|
|
landlord_cards = LandlordCards
|
|
}.
|
|
|
|
%% 出牌
|
|
play_cards(GameState, PlayerPid, Cards) ->
|
|
case validate_play(GameState, PlayerPid, Cards) of
|
|
true ->
|
|
NewState = update_game_state(GameState, PlayerPid, Cards),
|
|
check_game_end(NewState);
|
|
false ->
|
|
{error, invalid_play}
|
|
end.
|
|
|
|
%% 验证出牌
|
|
validate_play(GameState, PlayerPid, Cards) ->
|
|
case get_player_cards(GameState, PlayerPid) of
|
|
{ok, PlayerCards} ->
|
|
has_cards(PlayerCards, Cards) andalso
|
|
is_valid_play(Cards, GameState#game_state.last_play);
|
|
_ ->
|
|
false
|
|
end.
|
|
|
|
%% 判断牌型
|
|
get_card_type(Cards) ->
|
|
Sorted = sort_cards(Cards),
|
|
case analyze_pattern(Sorted) of
|
|
{Type, Value} -> {ok, Type, Value};
|
|
invalid -> {error, invalid_pattern}
|
|
end.
|
|
|
|
%% 内部函数
|
|
|
|
shuffle(List) ->
|
|
[X || {_, X} <- lists:sort([{rand:uniform(), N} || N <- List])].
|
|
|
|
analyze_pattern(Cards) ->
|
|
case length(Cards) of
|
|
1 -> {single, card_value(hd(Cards))};
|
|
2 -> analyze_pair(Cards);
|
|
3 -> analyze_triple(Cards);
|
|
4 -> analyze_four_cards(Cards);
|
|
_ -> analyze_complex_pattern(Cards)
|
|
end.
|
|
|
|
analyze_pair([{_, N}, {_, N}]) -> {pair, card_value({any, N})};
|
|
analyze_pair(_) -> invalid.
|
|
|
|
analyze_triple([{_, N}, {_, N}, {_, N}]) -> {triple, card_value({any, N})};
|
|
analyze_triple(_) -> invalid.
|
|
|
|
analyze_four_cards(Cards) ->
|
|
case group_cards(Cards) of
|
|
#{4 := [Value]} -> {bomb, card_value({any, Value})};
|
|
_ -> analyze_four_with_two(Cards)
|
|
end.
|
|
|
|
analyze_complex_pattern(Cards) ->
|
|
case is_straight(Cards) of
|
|
true -> {straight, highest_card_value(Cards)};
|
|
false -> analyze_other_patterns(Cards)
|
|
end.
|
|
|
|
%% 获取游戏状态
|
|
get_game_state(GameId) ->
|
|
% 简单实现,实际应该从某种存储中获取
|
|
{ok, #game_state{}}.
|
|
%% 初始化游戏状态
|
|
init_game_state() ->
|
|
% 创建一个空的游戏状态
|
|
#game_state{
|
|
players = [],
|
|
current_player = undefined,
|
|
last_play = [],
|
|
played_cards = [],
|
|
stage = waiting,
|
|
landlord_cards = []
|
|
}.
|
|
|
|
%% 判断出牌是否有效
|
|
is_valid_play(Cards, LastPlay) ->
|
|
% 如果是第一手牌,任何合法牌型都可以
|
|
case LastPlay of
|
|
[] -> is_valid_card_type(Cards);
|
|
{_, LastCards} ->
|
|
% 检查牌型是否相同且大小是否更大
|
|
{ok, Type1, Value1} = get_card_type(Cards),
|
|
{ok, Type2, Value2} = get_card_type(LastCards),
|
|
(Type1 =:= Type2 andalso Value1 > Value2) orelse
|
|
(Type1 =:= bomb andalso Type2 =/= bomb)
|
|
end.
|
|
|
|
%% 判断牌型是否合法
|
|
is_valid_card_type(Cards) ->
|
|
case get_card_type(Cards) of
|
|
{ok, _, _} -> true;
|
|
_ -> false
|
|
end.
|
|
|
|
%% 比较两手牌的大小
|
|
compare_cards(Cards1, Cards2) ->
|
|
{ok, Type1, Value1} = get_card_type(Cards1),
|
|
{ok, Type2, Value2} = get_card_type(Cards2),
|
|
if
|
|
Type1 =:= bomb andalso Type2 =/= bomb -> greater;
|
|
Type2 =:= bomb andalso Type1 =/= bomb -> lesser;
|
|
Type1 =:= Type2 andalso Value1 > Value2 -> greater;
|
|
Type1 =:= Type2 andalso Value1 < Value2 -> lesser;
|
|
true -> incomparable
|
|
end.
|
|
|
|
%% 分配角色
|
|
assign_roles(PlayerCards, LandlordIdx) ->
|
|
lists:map(
|
|
fun({Idx, {Pid, Cards}}) ->
|
|
Role = case Idx of
|
|
LandlordIdx -> landlord;
|
|
_ -> farmer
|
|
end,
|
|
{Pid, Cards, Role}
|
|
end,
|
|
lists:zip(lists:seq(1, length(PlayerCards)), PlayerCards)
|
|
).
|
|
|
|
%% 更新游戏状态
|
|
update_game_state(GameState, PlayerPid, Cards) ->
|
|
% 更新玩家手牌
|
|
UpdatedPlayers = lists:map(
|
|
fun({Pid, PlayerCards, Role}) when Pid =:= PlayerPid ->
|
|
{Pid, PlayerCards -- Cards, Role};
|
|
(Player) -> Player
|
|
end,
|
|
GameState#game_state.players
|
|
),
|
|
|
|
% 更新出牌记录和当前玩家
|
|
NextPlayer = get_next_player(GameState, PlayerPid),
|
|
GameState#game_state{
|
|
players = UpdatedPlayers,
|
|
current_player = NextPlayer,
|
|
last_play = {PlayerPid, Cards},
|
|
played_cards = [{PlayerPid, Cards} | GameState#game_state.played_cards]
|
|
}.
|
|
|
|
%% 获取下一个玩家
|
|
get_next_player(GameState, CurrentPid) ->
|
|
Players = GameState#game_state.players,
|
|
PlayerPids = [Pid || {Pid, _, _} <- Players],
|
|
CurrentIdx = get_player_index(PlayerPids, CurrentPid),
|
|
lists:nth(1 + (CurrentIdx rem length(PlayerPids)), PlayerPids).
|
|
|
|
%% 获取玩家索引
|
|
get_player_index(PlayerPids, TargetPid) ->
|
|
{Idx, _} = lists:foldl(
|
|
fun(Pid, {Idx, Found}) ->
|
|
case Pid =:= TargetPid of
|
|
true -> {Idx, Idx};
|
|
false -> {Idx + 1, Found}
|
|
end
|
|
end,
|
|
{1, not_found},
|
|
PlayerPids
|
|
),
|
|
Idx.
|
|
|
|
%% 检查游戏是否结束
|
|
check_game_end(GameState) ->
|
|
case lists:any(
|
|
fun({_, Cards, _}) -> length(Cards) =:= 0 end,
|
|
GameState#game_state.players
|
|
) of
|
|
true ->
|
|
Winner = lists:keyfind(0, 2, GameState#game_state.players),
|
|
{game_over, Winner, GameState#game_state{stage = finished}};
|
|
false ->
|
|
{continue, GameState}
|
|
end.
|
|
|
|
%% 获取玩家手牌
|
|
get_player_cards(GameState, PlayerPid) ->
|
|
case lists:keyfind(PlayerPid, 1, GameState#game_state.players) of
|
|
{_, Cards, _} -> {ok, Cards};
|
|
false -> {error, player_not_found}
|
|
end.
|
|
|
|
%% 检查玩家是否有这些牌
|
|
has_cards(PlayerCards, CardsToPlay) ->
|
|
lists:all(
|
|
fun(Card) ->
|
|
Count = count_card(PlayerCards, Card),
|
|
CountToPlay = count_card(CardsToPlay, Card),
|
|
Count >= CountToPlay
|
|
end,
|
|
lists:usort(CardsToPlay)
|
|
).
|
|
|
|
%% 计算特定牌的数量
|
|
count_card(Cards, TargetCard) ->
|
|
length([Card || Card <- Cards, Card =:= TargetCard]).
|
|
|
|
%% 分析四带二
|
|
analyze_four_with_two(Cards) ->
|
|
Grouped = group_cards(Cards),
|
|
case maps:get(4, Grouped, []) of
|
|
[Value] ->
|
|
case length(Cards) of
|
|
6 -> {four_with_two, card_value({any, Value})};
|
|
_ -> invalid
|
|
end;
|
|
_ -> invalid
|
|
end.
|
|
|
|
%% 分组牌
|
|
group_cards(Cards) ->
|
|
lists:foldl(
|
|
fun({_, Number}, Acc) ->
|
|
Count = maps:get(Number, Acc, 0) + 1,
|
|
Acc#{Number => Count}
|
|
end,
|
|
#{},
|
|
Cards
|
|
).
|
|
|
|
%% 获取最高牌值
|
|
highest_card_value(Cards) ->
|
|
lists:max([card_value(Card) || Card <- Cards]).
|
|
|
|
%% 牌值转换
|
|
card_value({_, "3"}) -> 3;
|
|
card_value({_, "4"}) -> 4;
|
|
card_value({_, "5"}) -> 5;
|
|
card_value({_, "6"}) -> 6;
|
|
card_value({_, "7"}) -> 7;
|
|
card_value({_, "8"}) -> 8;
|
|
card_value({_, "9"}) -> 9;
|
|
card_value({_, "10"}) -> 10;
|
|
card_value({_, "J"}) -> 11;
|
|
card_value({_, "Q"}) -> 12;
|
|
card_value({_, "K"}) -> 13;
|
|
card_value({_, "A"}) -> 14;
|
|
card_value({_, "2"}) -> 15;
|
|
card_value({_, "小王"}) -> 16;
|
|
card_value({_, "大王"}) -> 17;
|
|
card_value({any, Value}) -> card_value({"♠", Value}).
|
|
|
|
%% 检查是否为顺子
|
|
is_straight(Cards) ->
|
|
Values = [card_value(Card) || Card <- Cards],
|
|
SortedValues = lists:sort(Values),
|
|
length(SortedValues) >= 5 andalso
|
|
lists:max(SortedValues) =< 14 andalso % A以下的牌才能组成顺子
|
|
SortedValues =:= lists:seq(hd(SortedValues), lists:last(SortedValues)).
|
|
|
|
%% 分析其他牌型
|
|
analyze_other_patterns(Cards) ->
|
|
% 实现其他复杂牌型的分析,如飞机、连对等
|
|
% 简单实现,实际应该有更复杂的逻辑
|
|
invalid.
|
|
|
|
%% 排序牌
|
|
sort_cards(Cards) ->
|
|
lists:sort(fun(A, B) -> card_value(A) =< card_value(B) end, Cards).
|