game_tavla_bot.erl 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. -module(game_tavla_bot).
  2. -author('Maxim Sokhatsky <maxim@synrc.com>').
  3. -behaviour(gen_server).
  4. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  5. -export([start/3, start_link/3, robot_init/1, init_state/2, join_game/1, get_session/1,
  6. send_message/2, call_rpc/2, do_skip/2, do_rematch/1, first_move_table/0, follow_board/3 ]).
  7. -include_lib("server/include/log.hrl").
  8. -include_lib("server/include/conf.hrl").
  9. -include_lib("server/include/basic_types.hrl").
  10. -include_lib("server/include/requests.hrl").
  11. -include_lib("server/include/game_tavla.hrl").
  12. -include_lib("server/include/game_okey.hrl").
  13. -include_lib("server/include/settings.hrl").
  14. -record(state, {
  15. moves = 0 :: integer(),
  16. %% started = false :: boolean(),
  17. %% next_initiated = false :: boolean(),
  18. table_id :: integer(),
  19. is_robot = true :: boolean(),
  20. board :: list(tuple('Color'(), integer) | null),
  21. user :: #'PlayerInfo'{},
  22. player_color :: integer(),
  23. players,
  24. uid :: 'PlayerId'(),
  25. owner :: pid(),
  26. owner_mon :: 'MonitorRef'(),
  27. session :: pid(),
  28. gid :: 'GameId'(),
  29. bot :: pid(),
  30. conn :: pid(),
  31. hand :: list(),
  32. running_requests = dict:new() :: any(),
  33. delay :: integer(),
  34. mode :: atom(),
  35. request_id = 0,
  36. confirmation :: yes_exit | no_exit | no}).
  37. % gen_server
  38. send_message(Pid, Message) -> gen_server:call(Pid, {send_message, Message}).
  39. call_rpc(Pid, Message) -> gen_server:call(Pid, {call_rpc, Message}).
  40. get_session(Pid) -> gen_server:call(Pid, get_session).
  41. init_state(Pid, Situation) -> gen_server:cast(Pid, {init_state, Situation}).
  42. join_game(Pid) -> gen_server:cast(Pid, join_game).
  43. start(Owner, PlayerInfo, GameId) -> gen_server:start(?MODULE, [Owner, PlayerInfo, GameId], []).
  44. start_link(Owner, PlayerInfo, GameId) -> gen_server:start_link(?MODULE, [Owner, PlayerInfo, GameId], []).
  45. init([Owner, PlayerInfo, GameId]) ->
  46. {ok, SPid} = game_session:start_link(self()),
  47. game_session:bot_session_attach(SPid, PlayerInfo),
  48. UId = PlayerInfo#'PlayerInfo'.id,
  49. ?INFO("BOTMODULE ~p started with game_session pid ~p", [UId,SPid]),
  50. {ok, #state{user = PlayerInfo, uid = UId, owner = Owner, gid = GameId, session = SPid}}.
  51. handle_call({send_message, Msg0}, _From, State) ->
  52. BPid = State#state.bot,
  53. Msg = flashify(Msg0),
  54. BPid ! Msg,
  55. {reply, ok, State};
  56. handle_call({call_rpc, Msg}, From, State) ->
  57. RR = State#state.running_requests,
  58. Id = State#state.request_id + 1,
  59. Self = self(),
  60. RR1 = dict:store(Id, From, RR),
  61. proc_lib:spawn_link(fun() ->
  62. Res = try
  63. Answer = game_session:process_request(State#state.session, "TAVLA BOT", Msg),
  64. {reply, Id, Answer}
  65. catch
  66. _Err:Reason -> {reply, Id, {error, Reason}}
  67. end,
  68. gen_server:call(Self, Res)
  69. end),
  70. {noreply, State#state{running_requests = RR1, request_id = Id}};
  71. handle_call({reply, Id, Answer}, _From, State) ->
  72. RR = State#state.running_requests,
  73. From = dict:fetch(Id, RR),
  74. gen_server:reply(From, Answer),
  75. {reply, ok, State};
  76. handle_call(get_session, _From, State) ->
  77. {reply, State#state.session, State};
  78. % {ok, SPid} = game_session:start_link(self()),
  79. % game_session:bot_session_attach(SPid, State#state.user),
  80. % {reply, State#state.session, State#state{session = SPid}};
  81. handle_call(Request, _From, State) ->
  82. Reply = ok,
  83. ?INFO("unknown call: ~p", [Request]),
  84. {reply, Reply, State}.
  85. handle_cast(join_game, State) ->
  86. Mon = erlang:monitor(process, State#state.owner),
  87. UId = State#state.uid,
  88. GId = State#state.gid,
  89. ?INFO("Init State User ~p",[State#state.user]),
  90. BPid = proc_lib:spawn_link(game_tavla_bot, robot_init, [#state{gid = GId, uid = UId, conn = self(), table_id = State#state.table_id, user = State#state.user}]),
  91. BPid ! join_game,
  92. {noreply, State#state{bot = BPid, owner_mon = Mon}};
  93. handle_cast(Msg, State) ->
  94. ?INFO("unknown cast: ~p", [Msg]),
  95. {noreply, State}.
  96. handle_info({'DOWN', Ref, process, _, Reason}, State = #state{owner_mon = OMon}) when OMon == Ref ->
  97. ?INFO("relay goes down with reason ~p so does bot", [Reason]),
  98. {stop, Reason, State};
  99. handle_info({send_message,M}, State) ->
  100. {noreply, State};
  101. handle_info(Info, State) ->
  102. {stop, {unrecognized_info, Info}, State}.
  103. terminate(_Reason, _State) -> ok.
  104. code_change(_OldVsn, State, _Extra) -> {ok, State}.
  105. % loops
  106. robot_init(State) ->
  107. ?INFO("Robot Init User Info ~p",[State#state.user]),
  108. robot_init_loop(State).
  109. robot_init_loop(State) -> % receiving messages from relay
  110. S = State#state.conn,
  111. Id = State#state.uid,
  112. GameId = State#state.gid,
  113. receive
  114. join_game ->
  115. case call_rpc(S, #join_game{game = GameId}) of
  116. #'TableInfo'{game = _Atom} -> tavla_client_loop(State);
  117. _Err -> ?INFO("ID: ~p failed take with msg ~p", [Id, _Err]),
  118. erlang:error(robot_cant_join_game)
  119. end
  120. end.
  121. tavla_client_loop(State) -> % incapsulate tavla protocol
  122. timer:sleep(300),
  123. S = State#state.conn,
  124. GameId = State#state.gid,
  125. Id = State#state.uid,
  126. MyColor = State#state.player_color,
  127. GameMode = State#state.mode,
  128. receive
  129. #game_event{event = <<"tavla_next_turn">>, args = Params} = Msg ->
  130. TableId = proplists:get_value(table_id, Params, 0),
  131. case TableId == State#state.table_id of true ->
  132. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  133. case check_can_roll(GameMode) of
  134. true ->
  135. case fix_color(proplists:get_value(color, Params)) of
  136. MyColor ->
  137. ?INFO("TAVLABOT ~p Doing roll", [Id]),
  138. roll_action(State,TableId),
  139. tavla_client_loop(State);
  140. _ -> tavla_client_loop(State)
  141. end;
  142. false ->
  143. ?INFO("TAVLABOT ~p Ignoring tavla_next_turn (paired mode). Server rolls automaticly.", [Id]),
  144. tavla_client_loop(State)
  145. end;
  146. _ -> tavla_client_loop(State) end;
  147. #game_event{event = <<"tavla_moves">>, args = Params} = Msg ->
  148. TableId = proplists:get_value(table_id, Params, 0),
  149. case TableId == State#state.table_id of true ->
  150. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  151. PlayerColor = fix_color(proplists:get_value(color, Params)),
  152. From = proplists:get_value(from, Params),
  153. To = proplists:get_value(to, Params),
  154. %% ?INFO("board before moves: ~p", [State#state.board]),
  155. Board = reverse_board(State#state.board, PlayerColor),
  156. FromR=rel(From,PlayerColor), ToR=rel(To,PlayerColor),
  157. NewBoard = reverse_board(follow_board(Board,FromR,ToR,PlayerColor), PlayerColor),
  158. %% ?INFO("board after moves: ~p", [NewBoard]),
  159. tavla_client_loop(State#state{board = NewBoard,moves = State#state.moves + 1});
  160. _ -> tavla_client_loop(State) end;
  161. #game_event{event = <<"tavla_turn_timeout">>, args = Params} = Msg ->
  162. TableId = proplists:get_value(table_id, Params, 0),
  163. case TableId == State#state.table_id of true ->
  164. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  165. PlayerColor = fix_color(proplists:get_value(color, Params)),
  166. Moves = ext_to_moves(proplists:get_value(moves, Params)),
  167. %% ?INFO("board before moves: ~p", [State#state.board]),
  168. Board = reverse_board(State#state.board, PlayerColor),
  169. F = fun({From, To}, B) ->
  170. FromR = rel(From, PlayerColor), ToR = rel(To, PlayerColor),
  171. follow_board(B, FromR, ToR, PlayerColor)
  172. end,
  173. NewBoard = reverse_board(lists:foldl(F, Board, Moves), PlayerColor),
  174. %% ?INFO("board after moves: ~p", [NewBoard]),
  175. tavla_client_loop(State#state{board = NewBoard, moves = State#state.moves + length(Moves)});
  176. _ -> tavla_client_loop(State) end;
  177. %% #game_event{event = <<"tavla_vido_request">>, args = Params} ->
  178. %% TableId = proplists:get_value(table_id, Params, 0),
  179. %% case TableId == State#state.table_id of true ->
  180. %%
  181. %% % ?INFO("tavla moves: ~p",[Params]),
  182. %% To = proplists:get_value(from, Params),
  183. %% vido(State,To,TableId),
  184. %% tavla_client_loop(State);
  185. %%
  186. %% _ -> tavla_client_loop(State) end;
  187. #game_event{event = <<"tavla_surrender_request">>, args = Params} = Msg->
  188. TableId = proplists:get_value(table_id, Params, 0),
  189. case TableId == State#state.table_id of true ->
  190. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  191. % ?INFO("tavla moves: ~p",[Params]),
  192. To = proplists:get_value(from, Params),
  193. surrender(State,To,TableId),
  194. tavla_client_loop(State);
  195. _ -> tavla_client_loop(State) end;
  196. %% #game_event{event = <<"tavla_ack">>, args = Params} ->
  197. %% TableId = proplists:get_value(table_id, Params, 0),
  198. %% case TableId == State#state.table_id of true ->
  199. %% _To = proplists:get_value(from, Params),
  200. %% _Type = proplists:get_value(type, Params),
  201. %% tavla_client_loop(State);
  202. %%
  203. %% _ -> tavla_client_loop(State) end;
  204. #game_event{event = <<"tavla_game_player_state">>, args = Params} = Msg->
  205. TableId = proplists:get_value(table_id, Params, 0),
  206. case TableId == State#state.table_id of true ->
  207. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  208. PlayersColors = proplists:get_value(players_colors, Params),
  209. WhosMove = proplists:get_value(whos_move, Params),
  210. BoardRaw = proplists:get_value(board, Params),
  211. Dice = proplists:get_value(dice, Params),
  212. GameState = proplists:get_value(game_state, Params),
  213. Paused = proplists:get_value(paused, Params),
  214. case Paused of
  215. true -> wait_for_resume();
  216. false -> ok
  217. end,
  218. FoundMyself = lists:keyfind(Id, #tavla_color_info.name, PlayersColors),
  219. PlayerColor = fix_color(FoundMyself#tavla_color_info.color),
  220. ?INFO("TAVLABOT ~p BoardRaw: ~p", [Id, BoardRaw]),
  221. Board = if BoardRaw == null -> null;
  222. true -> ext_to_board(BoardRaw)
  223. end,
  224. State1 = State#state{board = Board, moves = 0, player_color=PlayerColor},
  225. ?INFO("TAVLABOT ~p player color: ~p",[Id, PlayerColor]),
  226. MyMove = lists:member(fix_color(PlayerColor), WhosMove),
  227. case {MyMove, GameState} of
  228. {true, <<"first_move_competition">>} ->
  229. roll_action(State1, TableId),
  230. tavla_client_loop(State1);
  231. {true, <<"waiting_for_roll">>} ->
  232. roll_action(State1, TableId),
  233. tavla_client_loop(State1);
  234. {true, <<"waiting_for_move">>} ->
  235. do_move(State1, Dice, TableId, PlayerColor),
  236. tavla_client_loop(State1#state{moves = 1});
  237. {_, <<"initializing">>} ->
  238. tavla_client_loop(State1);
  239. {_, <<"finished">>} ->
  240. tavla_client_loop(State1);
  241. {false, _} ->
  242. tavla_client_loop(State1)
  243. end;
  244. _ -> tavla_client_loop(State) end;
  245. #game_event{event = <<"tavla_game_started">>, args = Params} = Msg->
  246. TableId = proplists:get_value(table_id, Params, 0),
  247. case TableId == State#state.table_id of true ->
  248. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  249. Players = proplists:get_value(players, Params),
  250. BoardRaw = proplists:get_value(board, Params),
  251. Competition = proplists:get_value(do_first_move_competition_roll, Params),
  252. FoundMyself = lists:keyfind(Id, #tavla_color_info.name, Players),
  253. PlayerColor = fix_color(FoundMyself#tavla_color_info.color),
  254. ?INFO("TAVLABOT ~p game_started, color: ~p",[Id, PlayerColor]),
  255. ?INFO("TAVLABOT ~p game_started, BoardRaw: ~p",[Id, BoardRaw]),
  256. Board = if BoardRaw == null -> null;
  257. true -> ext_to_board(BoardRaw)
  258. end,
  259. if Competition -> roll_action(State,TableId); true -> do_nothing end,
  260. tavla_client_loop(State#state{board = Board, moves=0, player_color=PlayerColor});
  261. _ -> tavla_client_loop(State) end;
  262. #game_event{event = <<"tavla_won_first_move">>, args = Params} = Msg ->
  263. TableId = proplists:get_value(table_id, Params, 0),
  264. case TableId == State#state.table_id of true ->
  265. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  266. State2 = case fix_color(proplists:get_value(color, Params)) of
  267. MyColor ->
  268. ?INFO("TAVLABOT ~p : I won the first move order.", [Id]),
  269. case proplists:get_value(reroll, Params) of
  270. false ->
  271. Dice = proplists:get_value(dice, Params),
  272. ?INFO("TAVLABOT ~p Reroll is not needed. Doing the first move with dice:~p",[Id, Dice]),
  273. do_move(State,Dice,TableId,MyColor), State#state{moves = State#state.moves + 1};
  274. true ->
  275. ?INFO("TAVLABOT ~p Reroll needed. So doing it.",[Id]),
  276. roll_action(State, TableId)
  277. end;
  278. _ -> ?INFO("TAVLABOT ~p tavla_won_first_move: Ignore rolls (another color)",[Id]), State
  279. end,
  280. tavla_client_loop(State2);
  281. _ -> tavla_client_loop(State) end;
  282. #game_event{event = <<"tavla_rolls">>, args = Params} = Msg->
  283. TableId = proplists:get_value(table_id, Params, 0),
  284. case TableId == State#state.table_id of true ->
  285. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  286. State2 = case fix_color(proplists:get_value(color, Params)) of
  287. MyColor ->
  288. Dice = proplists:get_value(dices, Params),
  289. case Dice of
  290. [_A,_B] ->
  291. ?INFO("TAVLABOT ~p Doing moves with dice: ~p",[Id, Dice]),
  292. do_move(State,Dice,TableId,MyColor), State#state{moves = State#state.moves + 1};
  293. [_C] ->
  294. ?INFO("TAVLABOT ~p Ignore the roll (first move competition)",[Id]),
  295. State
  296. end;
  297. _ -> ?INFO("TAVLABOT ~p Ignore the roll (another color)",[Id]), State
  298. end,
  299. tavla_client_loop(State2);
  300. _ -> tavla_client_loop(State) end;
  301. #game_event{event = <<"player_left">>, args = Params} = Msg ->
  302. TableId = proplists:get_value(table_id, Params, 0),
  303. case TableId == State#state.table_id of true ->
  304. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  305. Replaced = proplists:get_value(bot_replaced, Params, false) orelse
  306. proplists:get_value(human_replaced, Params, false),
  307. case Replaced of
  308. false ->
  309. call_rpc(S, #logout{});
  310. true ->
  311. tavla_client_loop(State)
  312. end;
  313. _ -> tavla_client_loop(State) end;
  314. #game_event{event = <<"tavla_game_info">>, args = Args} = Msg->
  315. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  316. User = State#state.user,
  317. Mode = proplists:get_value(game_mode, Args),
  318. TableId = proplists:get_value(table_id, Args),
  319. ?INFO("TAVLABOT game_info ~p ~p",[self(),User]),
  320. Players = proplists:get_value(players, Args),
  321. SeriesConfirmMode = proplists:get_value(series_confirmation_mode, Args),
  322. ?INFO("TAVLABOT players: ~p",[Players]),
  323. Delay = get_delay(fast),
  324. CatchTID = case User == undefined of
  325. false -> FoundMyself = lists:keyfind(User#'PlayerInfo'.id,#'PlayerInfo'.id,Players),
  326. case FoundMyself of false -> State#state.table_id; _ -> TableId end;
  327. true -> ?INFO("ERROR USER in ~p is not set!",[self()]), undefined
  328. end,
  329. tavla_client_loop( State#state{table_id = CatchTID, delay = Delay, mode = Mode,
  330. players = Players, confirmation = SeriesConfirmMode});
  331. #game_event{event = <<"tavla_series_ended">>, args = Params} = Msg->
  332. TableId = proplists:get_value(table_id, Params, 0),
  333. case TableId == State#state.table_id of true ->
  334. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  335. tavla_client_loop(State);
  336. _ -> tavla_client_loop(State) end;
  337. #game_event{event = <<"tavla_game_ended">>, args = Params} = Msg->
  338. TableId = proplists:get_value(table_id, Params, 0),
  339. case TableId == State#state.table_id of true ->
  340. ?INFO("TAVLABOT ~p : Received message: ~p", [Id, Msg]),
  341. say_ready(State,TableId),
  342. tavla_client_loop(State);
  343. _ -> tavla_client_loop(State) end;
  344. Msg ->
  345. ?INFO("TAVLABOT ~p Received UNKNOWN message: ~p",[Id, Msg]),
  346. tavla_client_loop(State)
  347. end.
  348. fix_color(Color) -> case Color of 1 -> 2; 2 -> 1 end.
  349. check_can_roll(Mode) ->
  350. case Mode of
  351. <<"paired">> -> false;
  352. _ -> true
  353. end.
  354. % logic
  355. follow_board(Board,Moves,PlayerColor) ->
  356. lists:foldl(fun ({From,To}, Acc) -> follow_board(Acc,From,To,PlayerColor) end, Board, Moves).
  357. follow_board(Board,From,To,PlayerColor) -> % track the moves to keep board consistent
  358. FromCell = lists:nth(From + 1,Board),
  359. {FromColor,_FromCount} = case FromCell of
  360. null -> {0,0};
  361. _Else -> _Else
  362. end,
  363. BoardWithKicks = [ case No of
  364. From -> case Cell of
  365. {_Color,1} -> {{0,0},null};
  366. {Color,Count} -> {{0,0},{Color,Count-1}};
  367. _ -> ?INFO("Board: ~p From ~p To ~p",[Board,From,To]),
  368. ?INFO("follow_board: cant move from empty slot"), exit(self(),kill), {{0,0},null}
  369. end;
  370. To -> case Cell of
  371. null -> case FromColor of 0 -> {{0,0},null}; _ -> {{0,0},{FromColor,1}} end;
  372. {0,0} -> case FromColor of 0 -> {{0,0},null}; _ -> {{0,0},{FromColor,1}} end;
  373. {Color,Count} ->
  374. case Color =:= FromColor of
  375. true -> {{0,0},{Color,Count+1}};
  376. false -> case Count of
  377. 1 when FromColor =/= 0 -> {{Color,1},{FromColor,1}};
  378. _ -> ?INFO("Board: ~p From ~p To ~p",[Board,From,To]),
  379. ?INFO("follow_board: cant kick tower"), exit(self(),kill), {{0,0},{Color,Count}}
  380. end
  381. end
  382. end;
  383. _ -> {{0,0},Cell}
  384. end || {Cell,No} <- lists:zip(Board,lists:seq(0,27)) ],
  385. {KickColor,KickAmount} = lists:foldl(
  386. fun({{KC,KA},_},{Col,Sum}) ->
  387. case KC of
  388. 0 -> {Col,Sum};
  389. _ -> {KC,Sum+KA}
  390. end
  391. end,{0,0}, BoardWithKicks),
  392. %% ?INFO("Kick: ~p",[{KickColor,KickAmount}]),
  393. NewBoard = [ case {No,KickColor} of
  394. {25,_} when KickColor =/= 0 -> case Cell of
  395. null -> {KickColor,KickAmount};
  396. {_Color,Sum} -> {KickColor,KickAmount+Sum}
  397. end;
  398. _ -> Cell
  399. end || {{{_KC,_KA},Cell},No} <- lists:zip(BoardWithKicks,lists:seq(0,27))],
  400. NewBoard.
  401. all_in_home(Board,Color) ->
  402. Lates = lists:foldr(fun (A,Acc) ->
  403. case A of
  404. {null,_No} -> Acc;
  405. {{C,_Count},No} -> NotInHome = (((No > 0)and(No < 19)) or (No == 26)),
  406. case {C, NotInHome} of
  407. {Color,true} -> Acc + 1;
  408. _ -> Acc
  409. end
  410. end
  411. end,0,lists:zip(Board,lists:seq(0,27))),
  412. Lates =:= 0.
  413. make_decision(Board,Dices2,Color,TableId) -> % produces tavla moves
  414. [X,Y] = Dices2,
  415. Dices = case X =:= Y of true -> [X,X,X,X]; false -> Dices2 end,
  416. Decision = first_available_move(Board,Dices,Color,TableId),
  417. ?INFO("Decision: ~p",[Decision]),
  418. Decision.
  419. %% norm([A,B]) -> case A > B of true -> {A,B}; false -> {B,A} end.
  420. first_move_table() -> [{{6,6},[{13,7},{13,7},{24,18},{24,18}]}, % based on
  421. {{6,5},[{24,13}]},
  422. {{6,4},[{24,18},{13,9}]},
  423. {{6,3},[{24,18},{13,10}]},
  424. {{6,2},[{24,18},{13,11}]},
  425. {{6,1},[{13,7},{8,7}]},
  426. {{5,5},[{13,3},{13,3}]},
  427. {{5,4},[{24,20},{13,8}]},
  428. {{5,3},[{8,3},{5,3}]},
  429. {{5,2},[{24,22},{13,8}]},
  430. {{5,1},[{24,23},{13,8}]},
  431. {{4,4},[{24,20},{24,20},{13,9},{13,9}]},
  432. {{4,3},[{24,21},{13,9}]},
  433. {{4,2},[{8,4},{6,4}]},
  434. {{4,1},[{24,23},{13,9}]},
  435. {{3,3},[{24,21},{24,21},{13,10},{13,10}]},
  436. {{3,2},[{24,21},{13,11}]},
  437. {{3,1},[{8,5},{6,5}]},
  438. {{2,2},[{13,11},{13,11},{6,4},{6,4}]},
  439. {{2,1},[{13,11},{6,5}]},
  440. {{1,1},[{8,7},{8,7},{6,5},{6,5}]}
  441. ].
  442. %make_first_move(Dices,TableId,PlayerColor) ->
  443. % {_,Moves} = lists:keyfind(norm(Dices),1,first_move_table()),
  444. % case PlayerColor of
  445. % 1 -> [ #'TavlaAtomicMove'{table_id = TableId,from=25-From,to=25-To} || {From,To} <- Moves];
  446. % 2 -> [ #'TavlaAtomicMove'{table_id = TableId,from=From,to=To} || {From,To} <- Moves]
  447. % end.
  448. tactical_criteria(Board,Color) ->
  449. case all_in_home(Board,Color) of
  450. true -> finish;
  451. false -> case lists:nth(27,Board) of
  452. {Color,Count} -> kicks;
  453. _ -> race
  454. end
  455. end.
  456. rel(X,C) -> case C of 2 -> case X of 0->27;27->0;26->25;25->26;_->25-X end;
  457. 1 -> X end.
  458. reverse_board(Board,PlayerColor) ->
  459. case PlayerColor of
  460. 1 -> Board;
  461. 2 -> [BE|Rest] = Board, [WE,WK,BK|Rest1] = lists:reverse(Rest), [WE] ++ Rest1 ++ [WK,BK,BE]
  462. end.
  463. %% first_available_move(RealBoard,Dice,Color,TableId) -> Moves
  464. first_available_move(RealBoard,Dice,Color,TableId) ->
  465. RelativeBoard = reverse_board(RealBoard,Color),
  466. F = fun(Die, {MovesAcc, BoardAcc, FailedDiceAcc}) ->
  467. %% ?INFO("board: ~p", [BoardAcc]),
  468. Tactic = tactical_criteria(BoardAcc, Color),
  469. %% ?INFO("tactical criteria: ~p", [Tactic]),
  470. case find_move(Color, Die, Tactic, BoardAcc) of
  471. {Move, NewBoard} -> {[Move | MovesAcc], NewBoard, FailedDiceAcc};
  472. false -> {MovesAcc, BoardAcc, [Die | FailedDiceAcc]}
  473. end
  474. end,
  475. {List, Board, FailedDice} = lists:foldl(F, {[], RelativeBoard, []}, Dice),
  476. %% ?INFO("moves found: ~p",[{List, FailedDice}]),
  477. [#'TavlaAtomicMove'{from=rel(From,Color), to=rel(To,Color)} || {From,To} <- lists:reverse(List)] ++
  478. if length(FailedDice) == 0; length(FailedDice) == length(Dice) -> [];
  479. true -> first_available_move(reverse_board(Board,Color),FailedDice,Color,TableId) end.
  480. %% find_move(Die, Tactic, Board) -> {Move, NewBoard} | false
  481. find_move(Color, Die, Tactic, Board) ->
  482. OppColor = case Color of 1 -> 2; 2 -> 1 end,
  483. find_move(Color, OppColor, Die, Tactic, Board, lists:sublist(lists:zip(Board,lists:seq(0,27)),2,24)).
  484. find_move(_Color, _OppColor, _Die, _Tactic, _Board, []) -> false;
  485. find_move(Color, OppColor, Die, Tactic, Board, [{Cell, No} | Rest]) ->
  486. %% ?INFO("checking pos: ~p", [{Cell, No, Die, Tactic}]),
  487. case {Cell, Tactic} of
  488. {null,kicks} when No == Die -> ?INFO("found kick to empty: ~p",[{26,No}]), {{26,No}, follow_board(Board,26,No,Color)};
  489. {{OppColor,1},kicks} when No == Die -> ?INFO("found kick kick: ~p",[{26,No}]), {{26,No}, follow_board(Board,26,No,Color)};
  490. {{Color,_},kicks} when No == Die -> ?INFO("found kick over own: ~p",[{26,No}]), {{26,No}, follow_board(Board,26,No,Color)};
  491. {{Color,_},finish} when No + Die >= 25 -> ?INFO("found finish move: ~p",[{No,27}]), {{No,27}, follow_board(Board,No,27,Color)};
  492. {{Color,_},Tactic} when (Tactic==race orelse Tactic==finish) andalso (No + Die < 25) ->
  493. case lists:nth(No+Die+1, Board) of
  494. null -> ?INFO("found race to empty: ~p",[{No,No+Die}]), {{No,No+Die}, follow_board(Board,No,No+Die,Color)};
  495. {Color,_} -> ?INFO("found race over own: ~p",[{No,No+Die}]), {{No,No+Die}, follow_board(Board,No,No+Die,Color)};
  496. {OppColor,1} -> ?INFO("found race kick: ~p",[{No,No+Die}]), {{No,No+Die}, follow_board(Board,No,No+Die,Color)};
  497. _ -> find_move(Color, OppColor, Die, Tactic, Board, Rest)
  498. end;
  499. _ -> find_move(Color, OppColor, Die, Tactic, Board, Rest)
  500. end.
  501. %% make_end_series(Color,TableId,Board) ->
  502. %% lists:foldl(fun (A,Acc) -> ?INFO("~p / ~p~n",[A,Acc]), {Cell,No} = A, case Cell of null -> Acc;
  503. %% {Color,Count} -> Acc ++
  504. %% [ #'TavlaAtomicMove'{from=No, to=27} || X <- lists:seq(1,Count)]; _ -> Acc end
  505. %% end, [], lists:sublist(lists:zip(Board,lists:seq(0,27)),1,27) ).
  506. % actions
  507. do_rematch(State) ->
  508. S = State#state.conn,
  509. GameId = State#state.gid,
  510. ok = call_rpc(S, #rematch{game = GameId}).
  511. say_ready(State,TableId) ->
  512. S = State#state.conn,
  513. GameId = State#state.gid,
  514. ok = call_rpc(S, #game_action{game = GameId, action = tavla_ready, args = [{table_id,TableId}]}).
  515. vido(State,To,TableId) ->
  516. S = State#state.conn,
  517. GameId = State#state.gid,
  518. ok = call_rpc(S, #game_action{game = GameId,
  519. action = tavla_vido_answer,
  520. args = [{table_id,TableId},{from,State#state.uid},{to,To},{answer,false}]}).
  521. surrender(State,To,TableId) ->
  522. S = State#state.conn,
  523. GameId = State#state.gid,
  524. ok = call_rpc(S, #game_action{game = GameId,
  525. action = tavla_surrender_answer,
  526. args = [{table_id,TableId},{from,State#state.uid},{to,To},{answer,true}]}).
  527. roll_action(State,TableId) ->
  528. S = State#state.conn,
  529. GameId = State#state.gid,
  530. ok = call_rpc(S, #game_action{game = GameId, action = tavla_roll, args = [{table_id,TableId}]}).
  531. do_skip(State,TableId) ->
  532. S = State#state.conn,
  533. GameId = State#state.gid,
  534. call_rpc(S, #game_action{
  535. game = GameId,
  536. action = tavla_skip,
  537. args = [{table_id,TableId}]}).
  538. do_move(State, Dices,TableId,PlayerColor) ->
  539. Delay = State#state.delay,
  540. simulate_delay(take, Delay),
  541. S = State#state.conn,
  542. GameId = State#state.gid,
  543. Id = State#state.uid,
  544. % Decision = case State#state.moves > 5 of
  545. % false -> make_decision(State#state.board, Dices, PlayerColor,TableId);
  546. % true -> make_end_series(PlayerColor,TableId,State#state.board)
  547. % end,
  548. Decision = make_decision(State#state.board, Dices, PlayerColor,TableId),
  549. case Decision of
  550. [] ->
  551. ?INFO("TAVLABOT ~p : No moves available. Do nothing", [Id]),
  552. no_moves;
  553. _ ->
  554. ?INFO("TAVLABOT ~p : Moves: ~p", [Id, Decision]),
  555. Resp = call_rpc(S, #game_action{game = GameId,
  556. action = tavla_move,
  557. args = [{table_id, TableId},{player, Id},{moves, Decision }]}),
  558. ?INFO("TAVLABOT_DBG ~p : Server response: ~p", [Id, Resp]),
  559. {ok, Decision, Resp}
  560. end.
  561. flashify(R) when is_tuple(R) ->
  562. [RecName | Rest] = tuple_to_list(R),
  563. Rest1 = lists:map(fun
  564. (X) -> flashify(X)
  565. end, Rest),
  566. list_to_tuple([RecName | Rest1]);
  567. flashify([{Key, _Value} | _] = P) when is_atom(Key) ->
  568. lists:map(fun
  569. ({K, V}) when is_atom(K) -> {K, flashify(V)}
  570. end, P);
  571. flashify(A) when A == true -> A;
  572. flashify(A) when A == false -> A;
  573. flashify(A) when A == null -> A;
  574. flashify(A) when A == undefined -> A;
  575. flashify(A) when is_atom(A) ->
  576. list_to_binary(atom_to_list(A));
  577. flashify(Other) ->
  578. Other.
  579. time_to_sleep(_, Delay) ->
  580. erlang:trunc((Delay / 3) * 2).
  581. simulate_delay(Action, Delay) ->
  582. TheDelay = time_to_sleep(Action, Delay),
  583. receive
  584. #game_paused{action = <<"pause">>} ->
  585. wait_for_resume()
  586. after TheDelay ->
  587. ok
  588. end.
  589. wait_for_resume() ->
  590. receive
  591. #game_paused{action = <<"resume">>} ->
  592. ok
  593. end.
  594. ext_to_board(ExtBoard) ->
  595. [case Cell of
  596. null -> null;
  597. #tavla_checkers{color = C, number = N} -> {fix_color(C), N}
  598. end || Cell <- ExtBoard].
  599. ext_to_moves(ExtMoves) ->
  600. [{From, To} || #'TavlaAtomicMoveServer'{from = From, to = To} <- ExtMoves].
  601. get_delay(fast) -> {ok, Val} = nsm_db:get(config,"games/tavla/robot_delay_fast", 6000), Val;
  602. get_delay(normal) -> {ok, Val} = nsm_db:get(config,"games/tavla/robot_delay_normal", 9000), Val;
  603. get_delay(slow) -> {ok, Val} = nsm_db:get(config,"games/tavla/robot_delay_slow", 15000), Val.