test_tavla.erl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. -module(test_tavla).
  2. -author('Maxim Sokhatsky <maxim@synrc.com').
  3. -include_lib("server/include/conf.hrl").
  4. -include_lib("server/include/log.hrl").
  5. -include_lib("server/include/kamf.hrl").
  6. -include_lib("server/include/requests.hrl").
  7. -include_lib("server/include/classes.hrl").
  8. -include_lib("server/include/game_tavla.hrl").
  9. -include_lib("server/include/settings.hrl").
  10. -export([start/0]).
  11. -define(BT, 30000).
  12. -define(FLUSH_DELAY, 10).
  13. -define(SIM_DELAY, 10).
  14. -define(TCM, tc).
  15. -record(state, {
  16. conn,
  17. gid,
  18. uid,
  19. mode,
  20. hand,
  21. pileh,
  22. rounds,
  23. current_round,
  24. acker_fun = fun(_Event) -> continue end }).
  25. -record(bo, {pid, event, event_no, order }).
  26. start() ->
  27. MultiOwner = self(),
  28. process_flag(trap_exit, true),
  29. Clients = [ proc_lib:spawn_link(fun() ->
  30. fire_starter(MultiOwner, join_game_humans, normal)
  31. end) || _ <- lists:seq(1, 1) ],
  32. [ wait_for(C) || C <- Clients ].
  33. fire_starter(MultiOwner, CreateMode, RevealMode) ->
  34. process_flag(trap_exit, true),
  35. Host = localhost,
  36. Port = ?LISTEN_PORT,
  37. Owner = self(),
  38. Rematch = 1,
  39. case CreateMode of
  40. join_game_humans ->
  41. Humans = [<<"maxim">>,<<"alice">>], % see authored users in auth_server.erl
  42. {ok, GameId, _A} = game_manager:create_table(game_tavla, [{deny_robots,true},{rounds, 1}], Humans),
  43. ?INFO("created table for Tavla Game: gameid ~p",[{GameId,_A}]),
  44. Clients = [ proc_lib:spawn_link(fun() ->
  45. timer:sleep(crypto:rand_uniform(0, 10)),
  46. attach_and_join(Owner, Host, Port, GameId, Id, Rematch, RevealMode)
  47. end) || Id <- Humans ],
  48. ?INFO("Human Pids: ~p",[Clients]),
  49. Orders = []
  50. end,
  51. conductor(Orders, Clients),
  52. MultiOwner ! {self(), game_ended}.
  53. attach_and_join(Owner, Host, Port, GameId, OwnId, Rematch, Mode) ->
  54. put(mode, Mode),
  55. log(started),
  56. S1 = ?TCM:connect(Host, Port),
  57. TT = case OwnId of
  58. <<"maxim">> -> ?TEST_TOKEN;
  59. <<"alice">> -> ?TEST_TOKEN2
  60. end,
  61. #'PlayerInfo'{id = Id} = ?TCM:call_rpc(S1, #session_attach_debug{token = TT, id = OwnId}) ,
  62. log(connected),
  63. #'TableInfo'{game = _Atom} = ?TCM:call_rpc(S1, #join_game{game = GameId}) ,
  64. State = #state{conn = S1, gid = GameId, uid = Id, acker_fun = standard_acker(Owner)},
  65. play_round(State, Rematch),
  66. log(finished),
  67. ok = ?TCM:flush_events(?FLUSH_DELAY),
  68. ok = ?TCM:close(S1).
  69. % in tavla game we have only rounds (from 1 to 7) withing one game
  70. play_round(State0, Rematch) ->
  71. _Id = State0#state.uid,
  72. State = init_tavla_roundroll(State0),
  73. loop_and_restart(State),
  74. play_round(State, Rematch).
  75. loop_and_restart(State) ->
  76. LoopRes = tavla_client_loop(State#state{}),
  77. ?INFO("Human LoopRes: ~p",[LoopRes]),
  78. say_ready(State),
  79. case LoopRes of
  80. <<"done">> ->
  81. ?INFO("ID: ~p, next action done", [State#state.uid]),
  82. ok;
  83. <<"next_set">> ->
  84. ?INFO("ID: ~p, next action next_set", [State#state.uid]),
  85. ok;
  86. <<"next_round">> ->
  87. ?INFO("ID: ~p, next action next_round", [State#state.uid]),
  88. State1 = State#state{current_round = -1},
  89. check_ack(State1, game_ended, fun loop_and_restart/1, [State1])
  90. end.
  91. init_tavla_roundroll(State) ->
  92. _GameId = State#state.gid,
  93. receive
  94. #'game_event'{event = <<"tavla_game_info">>, args = Args, game = _GI} ->
  95. GT = proplists:get_value(game_type, Args),
  96. Rounds = proplists:get_value(rounds, Args),
  97. CurrentRound = proplists:get_value(current_round, Args),
  98. log(game_info),
  99. State#state{mode = GT, current_round = CurrentRound, rounds = Rounds}
  100. after ?BT ->
  101. ?INFO("ERROR: ~p", [{server_timeout, "game_event:tavla_game_info"}]),
  102. erlang:error({server_timeout, "game_event:tavla_game_info"})
  103. end.
  104. say_ready(State) ->
  105. S1 = State#state.conn,
  106. GameId = State#state.gid,
  107. <<"ok">> = ?TCM:call_rpc(S1, #game_action{game = GameId, action = tavla_ready, args = []}) .
  108. tavla_client_loop(State) ->
  109. Id = State#state.uid,
  110. receive
  111. #'game_event'{event = <<"tavla_next_turn">>, args = Args} ->
  112. Timeout = crypto:rand_uniform(0, ?SIM_DELAY),
  113. State1 = case {proplists:get_value(player, Args), proplists:get_value(can_challenge, Args)} of
  114. {Id, false} ->
  115. do_turn(State, Timeout);
  116. {_OtherId, _} ->
  117. State
  118. end,
  119. tavla_client_loop(State1);
  120. #'game_event'{event = <<"tavla_rolls">>} ->
  121. tavla_client_loop(State);
  122. #'game_event'{event = <<"tavla_moves">>} ->
  123. tavla_client_loop(State);
  124. #'game_event'{event = <<"tavla_ack">>} ->
  125. tavla_client_loop(State);
  126. #'game_event'{event = <<"tavla_game_ended">>, args = Args} ->
  127. %okey_round_ended_checks(Args, State),
  128. Reason = proplists:get_value(reason, Args),
  129. NextAction = proplists:get_value(next_action, Args),
  130. ?INFO("ID: ~p game ended, reason: ~p", [Id, Reason]),
  131. NextAction;
  132. #player_left{} ->
  133. tavla_client_loop(State);
  134. #'game_event'{event = <<"tavla_game_info">>, args = _Args} ->
  135. erlang:error({protocol_breach, okey_game_info});
  136. #'game_event'{event = <<"player_left">>} ->
  137. tavla_client_loop(State);
  138. _Msg ->
  139. ?INFO("the msg: ~p", [_Msg]),
  140. erlang:error({bot_received_unrecognized_message, _Msg})
  141. after ?BT ->
  142. log(server_timeouted),
  143. erlang:error({server_timeout, "tavla_client_loop_timeout"})
  144. end.
  145. do_turn(State, _Timeout) ->
  146. RollAnswer = do_roll(State),
  147. log(RollAnswer),
  148. MoveAnswer = do_move(State),
  149. log(MoveAnswer),
  150. State.
  151. do_roll(State) ->
  152. GameId = State#state.gid,
  153. S = State#state.conn,
  154. ZZZ = ?TCM:call_rpc(S, #game_action{
  155. game = GameId,
  156. action = tavla_roll,
  157. args = []}),
  158. ?INFO("ID: ~p roll result: ~p", [State#state.uid, ZZZ]),
  159. ok.
  160. do_move(State) ->
  161. GameId = State#state.gid,
  162. S = State#state.conn,
  163. Moves = [{from,2},{to, 25}],
  164. Player = State#state.uid,
  165. ZZZ = ?TCM:call_rpc(S, #game_action{
  166. game = GameId,
  167. action = tavla_move,
  168. args = [ {moves, Moves}, {player, Player} ]}),
  169. ?INFO("ID: ~p move result: ~p", [State#state.uid, ZZZ]),
  170. ok.
  171. log(Msg) ->
  172. ?TCM:log(Msg).
  173. wait_for(_C) ->
  174. receive
  175. {_, game_ended} ->
  176. ?INFO("client: game ended");
  177. {'EXIT', _, normal} = M ->
  178. ?INFO("client: normal ending: ~p", [M]);
  179. {'EXIT', _C, Reason} = M ->
  180. ?INFO("client: FAILURE message: ~p", [{M,_C,Reason}]),
  181. erlang:error(Reason);
  182. M ->
  183. ?INFO("client: unrecognized result: ~p", [M]),
  184. erlang:error(M)
  185. end.
  186. check_ack(State = #state{acker_fun = F}, Event, ContinueFun, Args) ->
  187. A = F(Event),
  188. case A of
  189. continue ->
  190. apply(ContinueFun, Args);
  191. {sleep, T} ->
  192. timer:sleep(T),
  193. apply(ContinueFun, Args);
  194. {do_and_continue, What, With} ->
  195. apply(What, [State] ++ With),
  196. apply(ContinueFun, Args);
  197. stop ->
  198. ?INFO("ID: ~p, Pid: ~p, check_ack. STOPPING THE BOT!!!", [State#state.uid, self()]),
  199. erlang:error({terminate, acker_stop})
  200. end.
  201. standard_acker(Owner) ->
  202. Self = self(),
  203. Ref = make_ref(),
  204. fun(Event) ->
  205. E = {event, Ref, Self, Event},
  206. ?INFO("standard acker. ~p ! ~p", [Owner, E]),
  207. Owner ! E,
  208. receive
  209. {ARef, Res} when Ref == ARef ->
  210. ?INFO("standard acker got '~p' answer on question ~p", [Res,{Owner, E}]),
  211. Res
  212. end
  213. end.
  214. update_events(Events, Pid, Event) ->
  215. F = [ X || {APid, AEvent, _} = X <- Events, Pid == APid, Event == AEvent ],
  216. case F of
  217. [] ->
  218. [{Pid, Event, 1} | Events];
  219. [{_,_,C} = X] ->
  220. L1 = lists:delete(X, Events),
  221. [{Pid, Event, C+1} | L1]
  222. end.
  223. match_event(Orders, Events, Pid, Event) ->
  224. Z = [ X || {APid, AEvent, _} = X <- Events, Pid == APid, Event == AEvent ],
  225. [{_, _, C}] = Z,
  226. Matched = [ Y || Y = #bo{pid = P, event = E} <- Orders, P == Pid, E == Event ],
  227. case Matched of
  228. [BO = #bo{event_no = EN} | _] when EN == C ->
  229. BO;
  230. _X ->
  231. false
  232. end.
  233. conductor(Orders, Clients) ->
  234. ?INFO("conductor init", []),
  235. Pairs = lists:zip(lists:seq(1, length(Clients)), Clients),
  236. Orders2 = lists:map(fun(O) ->
  237. {_, P} = lists:keyfind(O#bo.pid, 1, Pairs),
  238. O#bo{pid = P}
  239. end, Orders),
  240. conductor(Orders2, Clients, []).
  241. conductor(_Orders, [], _Events) ->
  242. ?INFO("conductor stop", []),
  243. ok;
  244. conductor(Orders, Clients, Events) ->
  245. receive
  246. {event, Ref, Pid, Event} = _E ->
  247. E2 = update_events(Events, Pid, Event),
  248. Z = match_event(Orders, E2, Pid, Event),
  249. case Z of
  250. #bo{order = Order} ->
  251. Pid ! {Ref, Order};
  252. _ ->
  253. Pid ! {Ref, continue}
  254. end,
  255. conductor(Orders, Clients, E2);
  256. {C, game_ended} ->
  257. ?INFO("conductor: game ended", []),
  258. conductor(Orders, lists:delete(C, Clients), Events);
  259. {'EXIT', C, normal} = M ->
  260. ?INFO("conductor: normal ending: ~p", [M]),
  261. conductor(Orders, lists:delete(C, Clients), Events);
  262. {'EXIT', C, {terminate, acker_stop}} = M ->
  263. ?INFO("conductor: removing client ~p because of ~p", [C, M]),
  264. conductor(Orders, lists:delete(C, Clients), Events);
  265. {'EXIT', _C, Reason} = M ->
  266. ?INFO("conductor: FAILURE message: ~p", [{M,_C,Reason}]),
  267. erlang:error(Reason);
  268. M ->
  269. ?INFO("conductor: unrecognized msg from client: ~p", [M]),
  270. erlang:error(M)
  271. end.