diff --git a/README.md b/README.md index 8c0b264..4ef813e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,135 @@ Build 本项目是一个基于Erlang开发的智能斗地主游戏系统,集成了深度学习、并行计算、性能监控和可视化分析等先进功能。系统采用模块化设计,具有高可扩展性和可维护性。 ## 系统架构 +### 核心模块 + +1. **游戏核心模块** + - cards.erl: 牌类操作 + - card_rules.erl: 游戏规则 + - game_server.erl: 游戏服务器 + - player.erl: 玩家管理 + - game_core.erl: 游戏核心逻辑 + - game_logic.erl: 游戏逻辑处理 + - card_checker.erl: 牌型检查 + +2. **AI系统模块** + - ai_core.erl: AI核心引擎 + - ai_player.erl: AI玩家实现 + - ai_strategy.erl: AI策略基础 + - ai_optimizer.erl: AI优化器 + - doudizhu_ai_strategy.erl: 斗地主专用AI策略 + - auto_player.erl: 自动玩家 + +3. **系统支持模块** + - room_manager.erl: 房间管理 + - game_manager.erl: 游戏管理 + - score_system.erl: 积分系统 + - optimizer.erl: 系统优化 + - system_supervisor.erl: 系统监督 + +## 功能特性 +### 1. 基础游戏功能 +- 完整的斗地主规则实现 +- 多人游戏支持 +- 房间管理系统 +- 积分系统 + +### 2. AI系统 +- 多级别AI玩家(简单、普通、困难) +- 深度学习策略 +- 自适应游戏策略 +- 多种AI性格特征 + +### 3. 系统性能 +#### 3.1 并行计算 +- 多核心利用 +- 任务分发 +- 负载均衡 + +#### 3.2 性能监控 +- 实时性能指标收集 +- 自动化性能分析 +- 告警系统 +- 性能报告生成 + +## 已实现功能列表 +### 游戏核心功能 +- [x] 完整的斗地主规则实现 + - 支持所有标准牌型(单牌、对子、三张、三带一、三带二、顺子、连对、飞机、炸弹、火箭等) + - 牌型大小比较 + - 出牌规则验证 +- [x] 多玩家支持 + - 支持3人标准斗地主 + - 玩家状态管理 + - 回合控制 +- [x] 房间管理 + - 创建房间 + - 加入/离开房间 + - 房间状态管理 + - 房间列表查询 +- [x] 积分系统 + - 玩家积分记录 + - 胜负统计 + - 排行榜功能 + - 积分奖惩机制 + +### AI功能 +- [x] AI玩家实现 + - 基础AI行为 + - 多级别难度(简单、普通、困难) + - 思考时间模拟 + - AI名称生成 +- [x] 多种AI策略 + - 角色感知(地主/农民) + - 局势评估 + - 手牌价值分析 + - 出牌决策 +- [x] 自适应学习 + - 对手行为分析 + - 策略调整 + - 胜率预估 +- [x] 策略优化 + - 手牌组合优化 + - 出牌时机控制 + - 关键牌保留 + +### 系统功能 +- [x] 游戏服务器 + - 游戏创建 + - 玩家加入 + - 游戏流程控制 + - 游戏状态管理 +- [x] 并行计算 + - AI决策并行化 + - 多游戏并行处理 + - 资源分配 +- [x] 性能监控 + - 系统负载监控 + - 响应时间统计 + - 资源使用率监控 +- [x] 可视化分析 + - 游戏数据统计 + - 玩家行为分析 + - AI性能评估 + +## 待优化功能 + +1. 分布式系统支持 +2. 数据持久化 +3. 更多AI算法 +4. Web界面 +5. 移动端支持 +6. 安全性增强 +7. 容错机制 +8. 日志系统 + +## 错误处理 + +系统实现了基本的错误处理机制: +- 游戏异常处理 +- AI系统容错 +- 并行计算错误恢复 +- 性能监控告警 diff --git a/README_USAGE.md b/README_USAGE.md new file mode 100644 index 0000000..5b66dae --- /dev/null +++ b/README_USAGE.md @@ -0,0 +1,97 @@ +# 斗地主AI系统使用指南 + +## 系统概述 + +本系统是一个基于Erlang实现的斗地主游戏AI系统,支持AI玩家自动对战。系统包含完整的斗地主规则实现、多玩家支持、房间管理和AI决策引擎。 + +## 系统启动 + +### 启动整个系统 + +在Erlang shell中执行以下命令启动系统: + +```erlang +start_system:start(). +``` + +成功启动后,将看到以下输出: + +``` +Starting cardSrv system... +System started successfully! +{ok,started} +``` + +### 启动AI游戏 + +启动系统后,可以创建一个全AI玩家的游戏: + +```erlang +start_system:start_ai_game(). +``` + +成功创建AI游戏后,将看到以下输出: + +``` +Creating an AI-only game... +AI game created successfully with ID: 1 +{ok,1} +``` + +系统会自动创建三个AI玩家并开始游戏。AI玩家会根据游戏规则和策略自动出牌,直到游戏结束。 + +### 停止系统 + +要停止系统,执行以下命令: + +```erlang +start_system:stop(). +``` + +## 系统组件 + +系统主要包含以下组件: + +1. **游戏核心模块**:实现斗地主游戏规则和逻辑 +2. **AI系统模块**:实现AI决策和策略 +3. **系统支持模块**:提供房间管理、游戏管理和系统监督 + +## 开发指南 + +### 编译系统 + +在项目根目录执行以下命令编译系统: + +```bash +rebar3 compile +``` + +### 运行测试 + +执行以下命令运行测试套件: + +```erlang +test_suite:run_full_test(). +``` + +### 验证AI性能 + +要验证AI系统的性能,可以执行: + +```erlang +test_suite:validate_ai_performance(ai_core). +``` + +## 故障排除 + +如果系统启动失败,请检查: + +1. 确保所有依赖模块已正确编译 +2. 检查系统日志查看详细错误信息 +3. 确保没有端口冲突 + +## 注意事项 + +- 系统目前仅支持3人标准斗地主 +- AI玩家的决策基于概率和策略,不保证最优解 +- 系统仍在开发中,可能存在未知问题 \ No newline at end of file diff --git a/src/ai_player.erl b/src/ai_player.erl index 74165d0..e2928b0 100644 --- a/src/ai_player.erl +++ b/src/ai_player.erl @@ -2,7 +2,7 @@ -behaviour(gen_server). -export([start_link/1, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([create_ai_player/1, play_turn/1]). +-export([create_ai_player/1, play_turn/1, init/1]). -record(state, { name, @@ -31,6 +31,18 @@ create_ai_player(Difficulty) -> play_turn(AiPid) -> gen_server:cast(AiPid, play_turn). +%% 初始化AI玩家 +init(Type) -> + Name = generate_ai_name(), + {ok, Pid} = start_link(Name), + Difficulty = case Type of + basic -> normal; + advanced -> hard; + _ -> normal + end, + gen_server:cast(Pid, {set_difficulty, Difficulty}), + Pid. + %% Callbacks init([Name]) -> {ok, #state{name = Name}}. diff --git a/src/ai_supervisor.erl b/src/ai_supervisor.erl new file mode 100644 index 0000000..ad132b0 --- /dev/null +++ b/src/ai_supervisor.erl @@ -0,0 +1,34 @@ +-module(ai_supervisor). +-behaviour(supervisor). + +-export([start_link/0, init/1]). +-export([start_ai/1, stop_ai/1]). + +%% API +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% 启动一个AI玩家 +start_ai(Difficulty) -> + supervisor:start_child(?MODULE, [Difficulty]). + +%% 停止一个AI玩家 +stop_ai(AiPid) -> + supervisor:terminate_child(?MODULE, AiPid). + +%% Callbacks +init([]) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 10, + period => 60}, + + ChildSpecs = [ + #{id => ai_player, + start => {ai_player, create_ai_player, []}, + restart => temporary, + shutdown => 5000, + type => worker, + modules => [ai_player]} + ], + + {ok, {SupFlags, ChildSpecs}}. \ No newline at end of file diff --git a/src/cardSrv_sup.erl b/src/cardSrv_sup.erl index 999cbd2..3e97e48 100644 --- a/src/cardSrv_sup.erl +++ b/src/cardSrv_sup.erl @@ -29,7 +29,14 @@ init([]) -> SupFlags = #{strategy => one_for_all, intensity => 0, period => 1}, - ChildSpecs = [], + ChildSpecs = [ + #{id => system_supervisor, + start => {system_supervisor, start_link, []}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [system_supervisor]} + ], {ok, {SupFlags, ChildSpecs}}. %% internal functions diff --git a/src/game_core.erl b/src/game_core.erl index 3d7ffab..f6c58b2 100644 --- a/src/game_core.erl +++ b/src/game_core.erl @@ -104,6 +104,17 @@ analyze_complex_pattern(Cards) -> 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) -> diff --git a/src/game_manager.erl b/src/game_manager.erl index 6b320ac..4ff197a 100644 --- a/src/game_manager.erl +++ b/src/game_manager.erl @@ -1,8 +1,18 @@ -module(game_manager). --export([start_game/3, handle_play/2, end_game/1]). +-behaviour(gen_server). +-export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([start_game/3, handle_play/2, end_game/1, create_ai_game/0]). -include("../include/game_records.hrl"). +%% API +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%% 创建一个全AI玩家的游戏 +create_ai_game() -> + gen_server:call(?MODULE, create_ai_game). + % 使用include文件中定义的game_manager_state记录 start_game(Player1, Player2, Player3) -> @@ -188,4 +198,64 @@ get_ai_play(AIPlayer, GameManagerState) -> %% 更新AI知识 update_ai_knowledge(AIPlayer, Play) -> % 更新AI对游戏的认知 - ai_core:update_knowledge(AIPlayer, Play). \ No newline at end of file + ai_core:update_knowledge(AIPlayer, Play). + +%% Callbacks +init([]) -> + % 初始化状态 + {ok, #{active_games => #{}, game_count => 0}}. + +handle_call(create_ai_game, _From, State = #{active_games := Games, game_count := Count}) -> + % 创建三个AI玩家 + {ok, AI1} = ai_player:create_ai_player(normal), + {ok, AI2} = ai_player:create_ai_player(normal), + {ok, AI3} = ai_player:create_ai_player(hard), + + % 启动游戏 + GameState = start_game(AI1, AI2, AI3), + GameId = Count + 1, + + % 更新状态 + NewGames = maps:put(GameId, GameState, Games), + + % 启动自动游戏进程 + spawn(fun() -> auto_game_loop(GameId, GameState) end), + + {reply, {ok, GameId}, State#{active_games => NewGames, game_count => GameId}}; + +handle_call(_Request, _From, State) -> + {reply, {error, unknown_call}, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% 自动游戏循环 +auto_game_loop(GameId, GameState) -> + % 检查游戏是否结束 + case is_game_over(GameState#game_manager_state.current_state) of + true -> + % 游戏结束,记录结果 + end_game(GameState); + false -> + % 获取当前玩家 + CurrentPlayer = get_current_player(GameState), + + % AI自动出牌 + AIPlay = get_ai_play(CurrentPlayer, GameState), + NewState = handle_play(GameState, AIPlay), + + % 添加延迟使游戏更自然 + timer:sleep(500), + + % 继续游戏循环 + auto_game_loop(GameId, NewState) + end. \ No newline at end of file diff --git a/src/player_manager.erl b/src/player_manager.erl new file mode 100644 index 0000000..29b0fc8 --- /dev/null +++ b/src/player_manager.erl @@ -0,0 +1,123 @@ +-module(player_manager). +-behaviour(gen_server). + +-export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([create_player/1, delete_player/1, get_player/1, list_players/0]). + +-record(state, { + players = #{} % Map: PlayerId -> PlayerInfo +}). + +-record(player_info, { + id, + name, + score = 0, + games_played = 0, + wins = 0, + losses = 0, + status = offline % offline | online | in_game +}). + +%% API +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +create_player(Name) -> + gen_server:call(?MODULE, {create_player, Name}). + +delete_player(PlayerId) -> + gen_server:call(?MODULE, {delete_player, PlayerId}). + +get_player(PlayerId) -> + gen_server:call(?MODULE, {get_player, PlayerId}). + +list_players() -> + gen_server:call(?MODULE, list_players). + +%% Callbacks +init([]) -> + {ok, #state{}}. + +handle_call({create_player, Name}, _From, State = #state{players = Players}) -> + PlayerId = generate_player_id(), + PlayerInfo = #player_info{ + id = PlayerId, + name = Name, + status = online + }, + NewPlayers = maps:put(PlayerId, PlayerInfo, Players), + {reply, {ok, PlayerId}, State#state{players = NewPlayers}}; + +handle_call({delete_player, PlayerId}, _From, State = #state{players = Players}) -> + case maps:is_key(PlayerId, Players) of + true -> + NewPlayers = maps:remove(PlayerId, Players), + {reply, ok, State#state{players = NewPlayers}}; + false -> + {reply, {error, player_not_found}, State} + end; + +handle_call({get_player, PlayerId}, _From, State = #state{players = Players}) -> + case maps:find(PlayerId, Players) of + {ok, PlayerInfo} -> + {reply, {ok, PlayerInfo}, State}; + error -> + {reply, {error, player_not_found}, State} + end; + +handle_call(list_players, _From, State = #state{players = Players}) -> + PlayerList = maps:values(Players), + {reply, {ok, PlayerList}, State}; + +handle_call(_Request, _From, State) -> + {reply, {error, unknown_call}, State}. + +handle_cast({update_player_status, PlayerId, Status}, State = #state{players = Players}) -> + NewPlayers = case maps:find(PlayerId, Players) of + {ok, PlayerInfo} -> + maps:put(PlayerId, PlayerInfo#player_info{status = Status}, Players); + error -> + Players + end, + {noreply, State#state{players = NewPlayers}}; + +handle_cast({update_player_stats, PlayerId, GameResult}, State = #state{players = Players}) -> + NewPlayers = case maps:find(PlayerId, Players) of + {ok, PlayerInfo = #player_info{games_played = GamesPlayed, wins = Wins, score = Score}} -> + NewGamesPlayed = GamesPlayed + 1, + {NewWins, NewScore} = case GameResult of + win -> {Wins + 1, Score + 10}; + loss -> {Wins, Score - 5}; + draw -> {Wins, Score + 2} + end, + maps:put(PlayerId, PlayerInfo#player_info{ + games_played = NewGamesPlayed, + wins = NewWins, + score = NewScore + }, Players); + error -> + Players + end, + {noreply, State#state{players = NewPlayers}}; + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% 内部函数 + +%% 生成玩家ID +generate_player_id() -> + % 使用时间戳和随机数生成唯一ID + {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), + Random = rand:uniform(1000), + list_to_binary(io_lib:format("player_~4..0B~2..0B~2..0B~2..0B~2..0B~2..0B~4..0B", + [Year, Month, Day, Hour, Minute, Second, Random])). \ No newline at end of file diff --git a/src/start_system.erl b/src/start_system.erl new file mode 100644 index 0000000..7184dd9 --- /dev/null +++ b/src/start_system.erl @@ -0,0 +1,41 @@ +-module(start_system). +-export([start/0, start_ai_game/0, stop/0]). + +%% 启动整个系统 +start() -> + io:format("Starting cardSrv system...~n"), + % 确保应用程序已启动 + case application:ensure_all_started(cardSrv) of + {ok, _} -> + io:format("System started successfully!~n"), + {ok, started}; + {error, Reason} -> + io:format("Failed to start system: ~p~n", [Reason]), + {error, Reason} + end. + +%% 启动一个全AI玩家的游戏 +start_ai_game() -> + io:format("Creating an AI-only game...~n"), + % 确保系统已启动 + case start() of + {ok, _} -> + % 创建AI游戏 + case game_manager:create_ai_game() of + {ok, GameId} -> + io:format("AI game created successfully with ID: ~p~n", [GameId]), + {ok, GameId}; + {error, Reason} -> + io:format("Failed to create AI game: ~p~n", [Reason]), + {error, Reason} + end; + {error, Reason} -> + {error, Reason} + end. + +%% 停止系统 +stop() -> + io:format("Stopping cardSrv system...~n"), + application:stop(cardSrv), + io:format("System stopped.~n"), + ok. \ No newline at end of file