elimination.erl 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. %%% -------------------------------------------------------------------
  2. %%% Author : Sergii Polkovnikov <serge.polkovnikov@gmail.com>
  3. %%% Description : The "Eliminate tournament" logic
  4. %%%
  5. %%% Created : Nov 02, 2012
  6. %%% -------------------------------------------------------------------
  7. %%% Terms explanation:
  8. %%% GameId - uniq identifier of the tournament. Type: integer().
  9. %%% PlayerId - registration number of a player in the tournament. Type: integer()
  10. %%% UserId - cross system identifier of a physical user. Type: binary() (or string()?).
  11. %%% TableId - uniq identifier of a table in the tournament. Used by the
  12. %%% tournament logic. Type: integer().
  13. %%% TableGlobalId - uniq identifier of a table in the system. Can be used
  14. %%% to refer to a table directly - without pointing to a tournament.
  15. %%% Type: integer()
  16. -module(elimination).
  17. -behaviour(gen_fsm).
  18. %% --------------------------------------------------------------------
  19. %% Include files
  20. %% --------------------------------------------------------------------
  21. -include_lib("server/include/basic_types.hrl").
  22. -include_lib("db/include/table.hrl").
  23. -include_lib("db/include/transaction.hrl").
  24. -include_lib("db/include/scoring.hrl").
  25. %% --------------------------------------------------------------------
  26. %% External exports
  27. -export([start/1, start/2, start_link/2, reg/2]).
  28. %% gen_fsm callbacks
  29. -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
  30. -export([table_message/3, client_message/2, client_request/2, client_request/3,
  31. system_request/2, system_request/3]).
  32. -record(state,
  33. {%% Static values
  34. game_id :: pos_integer(),
  35. trn_id :: term(),
  36. table_params :: proplists:proplist(),
  37. tours_plan :: list(integer()), %% Defines how many players will be passed to a next tour
  38. tours :: integer(),
  39. players_per_table :: integer(),
  40. quota_per_round :: integer(), %% Using for the calculation of qouta amount which be deducted from players every tour
  41. rounds_per_tour :: integer(), %% Using for the calculation of qouta amount which be deducted from players every tour
  42. speed :: fast | normal,
  43. awards :: list(), %% [FirtsPrize, SecondPrize, ThirdPrize], Prize = undefined | GiftId
  44. demo_mode :: boolean(), %% If true then results of tours will be generated randomly
  45. table_module :: atom(),
  46. game_type :: atom(), %% Using only for quota transactions
  47. game_mode :: atom(), %% Using only for quota transactions
  48. %% Dinamic values
  49. players, %% The register of tournament players
  50. tables, %% The register of tournament tables
  51. seats, %% Stores relation between players and tables seats
  52. tournament_table :: list(), %% [{TurnNum, TurnRes}], TurnRes = [{PlayerId, CommonPos, Points, Status}]
  53. table_id_counter :: pos_integer(),
  54. tour :: pos_integer(),
  55. cr_tab_requests :: dict(), %% {TableId, PlayersIds}
  56. reg_requests :: dict(), %% {PlayerId, From}
  57. tab_requests :: dict(), %% {RequestId, RequestContext}
  58. timer :: undefined | reference(),
  59. timer_magic :: undefined | reference(),
  60. tables_wl :: list(), %% Tables waiting list
  61. tables_results :: list() %% [{TableId, TableResult}]
  62. }).
  63. -record(player,
  64. {
  65. id :: pos_integer(),
  66. user_id,
  67. user_info :: #'PlayerInfo'{},
  68. is_bot :: boolean(),
  69. status :: active | eliminated
  70. }).
  71. -record(table,
  72. {
  73. id :: pos_integer(),
  74. global_id :: pos_integer(),
  75. pid :: pid(),
  76. relay :: {atom(), pid()}, %% {RelayMod, RelayPid}
  77. mon_ref :: reference(),
  78. state :: initializing | ready | in_process | finished,
  79. context :: term(), %% Context term of a table. For failover proposes.
  80. timer :: reference()
  81. }).
  82. -record(seat,
  83. {
  84. table :: pos_integer(),
  85. seat_num :: integer(),
  86. player_id :: undefined | pos_integer(),
  87. registered_by_table :: undefined | boolean(),
  88. connected :: undefined | boolean()
  89. }).
  90. -define(STATE_INIT, state_init).
  91. -define(STATE_WAITING_FOR_TABLES, state_waiting_for_tables).
  92. -define(STATE_WAITING_FOR_PLAYERS, state_waiting_for_players).
  93. -define(STATE_TURN_PROCESSING, state_turn_processing).
  94. -define(STATE_SHOW_SERIES_RESULT, state_show_series_result).
  95. -define(STATE_FINISHED, state_finished).
  96. -define(TABLE_STATE_INITIALIZING, initializing).
  97. -define(TABLE_STATE_READY, ready).
  98. -define(TABLE_STATE_IN_PROGRESS, in_progress).
  99. -define(TABLE_STATE_WAITING_NEW_ROUND, waiting_new_round).
  100. -define(TABLE_STATE_FINISHED, finished).
  101. -define(WAITING_PLAYERS_TIMEOUT, 3000). %% Time between all table was created and starting a turn
  102. -define(REST_TIMEOUT, 5000). %% Time between a round finish and start of a new one
  103. -define(SHOW_SERIES_RESULT_TIMEOUT, 15000). %% Time between a tour finish and start of a new one
  104. -define(SHOW_TOURNAMENT_RESULT_TIMEOUT, 15000). %% Time between last tour result showing and the tournament finish
  105. -define(TOURNAMENT_TYPE, elimination).
  106. -define(ROUNDS_PER_TOUR, 10).
  107. %% ====================================================================
  108. %% External functions
  109. %% ====================================================================
  110. start([GameId, Params]) -> start(GameId, Params).
  111. start(GameId, Params) ->
  112. gen_fsm:start(?MODULE, [GameId, Params, self()], []).
  113. start_link(GameId, Params) ->
  114. gen_fsm:start_link(?MODULE, [GameId, Params, self()], []).
  115. reg(Pid, User) ->
  116. client_request(Pid, {join, User}, 10000).
  117. table_message(Pid, TableId, Message) ->
  118. gen_fsm:send_all_state_event(Pid, {table_message, TableId, Message}).
  119. client_message(Pid, Message) ->
  120. gen_fsm:send_all_state_event(Pid, {client_message, Message}).
  121. client_request(Pid, Message) ->
  122. client_request(Pid, Message, 5000).
  123. client_request(Pid, Message, Timeout) ->
  124. gen_fsm:sync_send_all_state_event(Pid, {client_request, Message}, Timeout).
  125. system_request(Pid, Message) ->
  126. system_request(Pid, Message, 5000).
  127. system_request(Pid, Message, Timeout) ->
  128. gen_fsm:sync_send_all_state_event(Pid, {system_request, Message}, Timeout).
  129. %% ====================================================================
  130. %% Server functions
  131. %% ====================================================================
  132. init([GameId, Params, _Manager]) ->
  133. gas:info(?MODULE,"TRN_ELIMINATION <~p> Init started",[GameId]),
  134. Registrants = get_param(registrants, Params),
  135. QuotaPerRound = get_param(quota_per_round, Params),
  136. RoundsPerTour = get_param(rounds_per_tour, Params),
  137. Tours = get_param(tours, Params),
  138. ToursPlan = get_param(plan, Params),
  139. PlayersPerTable = get_param(players_per_table, Params),
  140. Speed = get_param(speed, Params),
  141. GameType = get_param(game_type, Params),
  142. GameMode = get_param(game_mode, Params),
  143. Awards = get_param(awards, Params),
  144. TableParams = get_param(table_params, Params),
  145. TableModule = get_param(table_module, Params),
  146. DemoMode = get_option(demo_mode, Params, false),
  147. TrnId = get_option(trn_id, Params, undefined),
  148. [gas:info(?MODULE,"TRN_ELIMINATION_DBG <~p> Parameter <~p> : ~p", [GameId, P, V]) ||
  149. {P, V} <- Params],
  150. Players = setup_players(Registrants),
  151. PlayersIds = get_players_ids(Players),
  152. TTable = ttable_init(PlayersIds),
  153. gas:info(?MODULE,"TRN_ELIMINATION_DBG <~p> TTable: ~p", [GameId, TTable]),
  154. gas:info(?MODULE,"TRN_ELIMINATION <~p> started. Pid:~p", [GameId, self()]),
  155. gen_fsm:send_all_state_event(self(), go),
  156. {ok, ?STATE_INIT, #state{game_id = GameId,
  157. trn_id = TrnId,
  158. table_params = TableParams,
  159. quota_per_round = QuotaPerRound,
  160. rounds_per_tour = RoundsPerTour,
  161. tours_plan = ToursPlan,
  162. tours = Tours,
  163. players_per_table = PlayersPerTable,
  164. speed = Speed,
  165. awards = Awards,
  166. demo_mode = DemoMode,
  167. table_module = TableModule,
  168. game_type = GameType,
  169. game_mode = GameMode,
  170. players = Players,
  171. tournament_table = TTable,
  172. table_id_counter = 1
  173. }}.
  174. %%===================================================================
  175. handle_event(go, ?STATE_INIT, #state{game_id = GameId, trn_id = TrnId,
  176. game_type = GameType} = StateData) ->
  177. gas:info(?MODULE,"TRN_ELIMINATION <~p> Received a directive to starting the tournament.", [GameId]),
  178. GProcVal = #game_table{game_type = GameType,
  179. game_process = self(),
  180. game_module = ?MODULE,
  181. id = GameId,
  182. trn_id = TrnId,
  183. age_limit = 100,
  184. game_mode = undefined,
  185. game_speed = undefined,
  186. feel_lucky = false,
  187. owner = undefined,
  188. creator = undefined,
  189. rounds = undefined,
  190. pointing_rules = [],
  191. pointing_rules_ex = [],
  192. users = [],
  193. name = "Elimination Tournament - " ++ erlang:integer_to_list(GameId) ++ " "
  194. },
  195. gproc:reg({p,l,self()}, GProcVal),
  196. init_tour(1, StateData);
  197. handle_event({client_message, Message}, StateName, #state{game_id = GameId} = StateData) ->
  198. gas:info(?MODULE,"TRN_ELIMINATION <~p> Received the message from a client: ~p.", [GameId, Message]),
  199. handle_client_message(Message, StateName, StateData);
  200. handle_event({table_message, TableId, Message}, StateName, #state{game_id = GameId} = StateData) ->
  201. gas:info(?MODULE,"TRN_ELIMINATION <~p> Received the message from table <~p>: ~p.", [GameId, TableId, Message]),
  202. handle_table_message(TableId, Message, StateName, StateData);
  203. handle_event(Message, StateName, #state{game_id = GameId} = StateData) ->
  204. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled message(event) received in state <~p>: ~p.",
  205. [GameId, StateName, Message]),
  206. {next_state, StateName, StateData}.
  207. handle_sync_event({client_request, Request}, From, StateName, #state{game_id = GameId} = StateData) ->
  208. gas:info(?MODULE,"TRN_ELIMINATION <~p> Received the request from a client: ~p.", [GameId, Request]),
  209. handle_client_request(Request, From, StateName, StateData);
  210. handle_sync_event({system_request, Request}, From, StateName, #state{game_id = GameId} = StateData) ->
  211. gas:info(?MODULE,"TRN_ELIMINATION <~p> Received the request from the system: ~p.", [GameId, Request]),
  212. handle_system_request(Request, From, StateName, StateData);
  213. handle_sync_event(Request, From, StateName, #state{game_id = GameId} = StateData) ->
  214. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled request(event) received in state <~p> from ~p: ~p.",
  215. [GameId, StateName, From, Request]),
  216. {reply, {error, unknown_request}, StateName, StateData}.
  217. %%===================================================================
  218. handle_info({'DOWN', MonRef, process, _Pid, _}, StateName,
  219. #state{game_id = GameId, tables = Tables} = StateData) ->
  220. case get_table_by_mon_ref(MonRef, Tables) of
  221. #table{id = TableId} ->
  222. gas:info(?MODULE,"TRN_ELIMINATION <~p> Table <~p> is down. Stopping", [GameId, TableId]),
  223. %% TODO: More smart handling (failover) needed
  224. {stop, {one_of_tables_down, TableId}, StateData};
  225. not_found ->
  226. {next_state, StateName, StateData}
  227. end;
  228. handle_info({rest_timeout, TableId}, StateName,
  229. #state{game_id = GameId, tables = Tables, table_module = TableModule} = StateData) ->
  230. gas:info(?MODULE,"TRN_ELIMINATION <~p> Time to start new round for table <~p>.", [GameId, TableId]),
  231. #table{pid = TablePid, state = TableState} = Table = fetch_table(TableId, Tables),
  232. if TableState == ?TABLE_STATE_WAITING_NEW_ROUND ->
  233. NewTable = Table#table{state = ?TABLE_STATE_IN_PROGRESS},
  234. NewTables = store_table(NewTable, Tables),
  235. send_to_table(TableModule, TablePid, start_round),
  236. {next_state, StateName, StateData#state{tables = NewTables}};
  237. true ->
  238. gas:info(?MODULE,"TRN_ELIMINATION <~p> Don't start new round at table <~p> because it is not waiting for start.",
  239. [GameId, TableId]),
  240. {next_state, StateName, StateData}
  241. end;
  242. handle_info({timeout, Magic}, ?STATE_WAITING_FOR_PLAYERS,
  243. #state{timer_magic = Magic, game_id = GameId} = StateData) ->
  244. gas:info(?MODULE,"TRN_ELIMINATION <~p> Time to start new turn.", [GameId]),
  245. start_turn(StateData);
  246. handle_info({timeout, Magic}, ?STATE_SHOW_SERIES_RESULT,
  247. #state{timer_magic = Magic, tour = Tour, tours = Tours,
  248. game_id = GameId} = StateData) ->
  249. if Tour == Tours ->
  250. gas:info(?MODULE,"TRN_ELIMINATION <~p> Time to finalize the tournament.", [GameId]),
  251. finalize_tournament(StateData);
  252. true ->
  253. NewTour = Tour + 1,
  254. gas:info(?MODULE,"TRN_ELIMINATION <~p> Time to initialize tour <~p>.", [GameId, NewTour]),
  255. init_tour(NewTour, StateData)
  256. end;
  257. handle_info({timeout, Magic}, ?STATE_FINISHED,
  258. #state{timer_magic = Magic, tables = Tables, game_id = GameId,
  259. table_module = TableModule} = StateData) ->
  260. gas:info(?MODULE,"TRN_ELIMINATION <~p> Time to stopping the tournament.", [GameId]),
  261. finalize_tables_with_disconnect(TableModule, Tables),
  262. {stop, normal, StateData#state{tables = [], seats = []}};
  263. handle_info(Message, StateName, #state{game_id = GameId} = StateData) ->
  264. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled message(info) received in state <~p>: ~p.",
  265. [GameId, StateName, Message]),
  266. {next_state, StateName, StateData}.
  267. %%===================================================================
  268. terminate(_Reason, _StateName, #state{game_id=GameId}=_StatData) ->
  269. gas:info(?MODULE,"TRN_ELIMINATION <~p> Shutting down at state: <~p>. Reason: ~p",
  270. [GameId, _StateName, _Reason]),
  271. ok.
  272. %%===================================================================
  273. code_change(_OldVsn, StateName, StateData, _Extra) ->
  274. {ok, StateName, StateData}.
  275. %% --------------------------------------------------------------------
  276. %%% Internal functions
  277. %% --------------------------------------------------------------------
  278. handle_client_message(Message, StateName, #state{game_id = GameId} = StateData) ->
  279. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled client message received in "
  280. "state <~p>: ~p.", [GameId, StateName, Message]),
  281. {next_state, StateName, StateData}.
  282. %%===================================================================
  283. handle_table_message(TableId, {player_connected, PlayerId},
  284. StateName,
  285. #state{seats = Seats} = StateData) ->
  286. case find_seats_by_player_id(PlayerId, Seats) of
  287. [#seat{seat_num = SeatNum}] ->
  288. NewSeats = update_seat_connect_status(TableId, SeatNum, true, Seats),
  289. {next_state, StateName, StateData#state{seats = NewSeats}};
  290. [] -> %% Ignoring the message
  291. {next_state, StateName, StateData}
  292. end;
  293. handle_table_message(TableId, {player_disconnected, PlayerId},
  294. StateName, #state{seats = Seats} = StateData) ->
  295. case find_seats_by_player_id(PlayerId, Seats) of
  296. [#seat{seat_num = SeatNum}] ->
  297. NewSeats = update_seat_connect_status(TableId, SeatNum, false, Seats),
  298. {next_state, StateName, StateData#state{seats = NewSeats}};
  299. [] -> %% Ignoring the message
  300. {next_state, StateName, StateData}
  301. end;
  302. handle_table_message(TableId, {table_created, Relay},
  303. ?STATE_WAITING_FOR_TABLES,
  304. #state{tables = Tables, seats = Seats, cr_tab_requests = TCrRequests,
  305. table_module = TableModule, reg_requests = RegRequests} = StateData) ->
  306. TabInitPlayers = dict:fetch(TableId, TCrRequests),
  307. NewTCrRequests = dict:erase(TableId, TCrRequests),
  308. %% Update status of players
  309. TabSeats = find_seats_by_table_id(TableId, Seats),
  310. F = fun(#seat{player_id = PlayerId} = S, Acc) ->
  311. case lists:member(PlayerId, TabInitPlayers) of
  312. true -> store_seat(S#seat{registered_by_table = true}, Acc);
  313. false -> Acc
  314. end
  315. end,
  316. NewSeats = lists:foldl(F, Seats, TabSeats),
  317. %% Process delayed registration requests
  318. TablePid = get_table_pid(TableId, Tables),
  319. F2 = fun(PlayerId, Acc) ->
  320. case dict:find(PlayerId, Acc) of
  321. {ok, From} ->
  322. gen_fsm:reply(From, {ok, {PlayerId, Relay, {TableModule, TablePid}}}),
  323. dict:erase(PlayerId, Acc);
  324. error -> Acc
  325. end
  326. end,
  327. NewRegRequests = lists:foldl(F2, RegRequests, TabInitPlayers),
  328. NewTables = update_created_table(TableId, Relay, Tables),
  329. case dict:size(NewTCrRequests) of
  330. 0 ->
  331. {TRef, Magic} = start_timer(?WAITING_PLAYERS_TIMEOUT),
  332. {next_state, ?STATE_WAITING_FOR_PLAYERS,
  333. StateData#state{tables = NewTables, seats = NewSeats, cr_tab_requests = NewTCrRequests,
  334. reg_requests = NewRegRequests, timer = TRef, timer_magic = Magic}};
  335. _ -> {next_state, ?STATE_WAITING_FOR_TABLES,
  336. StateData#state{tables = NewTables, seats = NewSeats,
  337. cr_tab_requests = NewTCrRequests, reg_requests = NewRegRequests}}
  338. end;
  339. handle_table_message(TableId, {round_finished, NewScoringState, _RoundScore, _TotalScore},
  340. ?STATE_TURN_PROCESSING,
  341. #state{tables = Tables, table_module = TableModule} = StateData) ->
  342. #table{pid = TablePid} = Table = fetch_table(TableId, Tables),
  343. TRef = erlang:send_after(?REST_TIMEOUT, self(), {rest_timeout, TableId}),
  344. NewTable = Table#table{context = NewScoringState, state = ?TABLE_STATE_WAITING_NEW_ROUND, timer = TRef},
  345. NewTables = store_table(NewTable, Tables),
  346. send_to_table(TableModule, TablePid, show_round_result),
  347. {next_state, ?STATE_TURN_PROCESSING, StateData#state{tables = NewTables}};
  348. handle_table_message(TableId, {game_finished, TableContext, _RoundScore, TotalScore},
  349. ?STATE_TURN_PROCESSING = StateName,
  350. #state{tables = Tables, tables_wl = WL, demo_mode = DemoMode,
  351. tables_results = TablesResults, table_module = TableModule
  352. } = StateData) ->
  353. TableScore = if DemoMode -> [{PlayerId, crypto:rand_uniform(1, 30)} || {PlayerId, _} <- TotalScore];
  354. true -> TotalScore
  355. end,
  356. NewTablesResults = [{TableId, TableScore} | TablesResults],
  357. #table{pid = TablePid, state = TableState, timer = TRef} = Table = fetch_table(TableId, Tables),
  358. if TableState == ?TABLE_STATE_WAITING_NEW_ROUND -> erlang:cancel_timer(TRef);
  359. true -> do_nothing
  360. end,
  361. NewTable = Table#table{context = TableContext, state = ?TABLE_STATE_FINISHED, timer = undefined},
  362. NewTables = store_table(NewTable, Tables),
  363. send_to_table(TableModule, TablePid, show_round_result),
  364. NewWL = lists:delete(TableId, WL),
  365. [send_to_table(TableModule, TPid, {playing_tables_num, length(NewWL)})
  366. || #table{pid = TPid, state = ?TABLE_STATE_FINISHED} <- tables_to_list(Tables)],
  367. if NewWL == [] ->
  368. process_tour_result(StateData#state{tables = NewTables,
  369. tables_results = NewTablesResults,
  370. tables_wl = []});
  371. true ->
  372. {next_state, StateName, StateData#state{tables = NewTables,
  373. tables_results = NewTablesResults,
  374. tables_wl = NewWL}}
  375. end;
  376. handle_table_message(TableId, {response, RequestId, Response},
  377. StateName,
  378. #state{game_id = GameId, tab_requests = TabRequests} = StateData) ->
  379. NewTabRequests = dict:erase(RequestId, TabRequests),
  380. case dict:find(RequestId, TabRequests) of
  381. {ok, ReqContext} ->
  382. gas:info(?MODULE,"TRN_ELIMINATION <~p> The a response received from table <~p>. "
  383. "RequestId: ~p. Request context: ~p. Response: ~p",
  384. [GameId, TableId, RequestId, ReqContext, Response]),
  385. handle_table_response(TableId, ReqContext, Response, StateName,
  386. StateData#state{tab_requests = NewTabRequests});
  387. error ->
  388. gas:error(?MODULE,"TRN_ELIMINATION <~p> Table <~p> sent a response for unknown request. "
  389. "RequestId: ~p. Response", []),
  390. {next_state, StateName, StateData#state{tab_requests = NewTabRequests}}
  391. end;
  392. handle_table_message(TableId, Message, StateName, #state{game_id = GameId} = StateData) ->
  393. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled table message received from table <~p> in "
  394. "state <~p>: ~p.", [GameId, TableId, StateName, Message]),
  395. {next_state, StateName, StateData}.
  396. %%===================================================================
  397. %% handle_table_response(_TableId, {register_player, PlayerId, TableId, SeatNum}, ok = _Response,
  398. %% StateName,
  399. %% #state{reg_requests = RegRequests, seats = Seats,
  400. %% tables = Tables} = StateData) ->
  401. %% Seat = fetch_seat(TableId, SeatNum, Seats),
  402. %% NewSeats = store_seat(Seat#seat{registered_by_table = true}, Seats),
  403. %% %% Send response to a client for a delayed request
  404. %% NewRegRequests =
  405. %% case dict:find(PlayerId, RegRequests) of
  406. %% {ok, From} ->
  407. %% #table{relay = Relay, pid = TablePid} = fetch_table(TableId, Tables),
  408. %% gen_fsm:reply(From, {ok, {PlayerId, Relay, {?TAB_MOD, TablePid}}}),
  409. %% dict:erase(PlayerId, RegRequests);
  410. %% error -> RegRequests
  411. %% end,
  412. %% {next_state, StateName, StateData#state{seats = NewSeats,
  413. %% reg_requests = NewRegRequests}};
  414. %% handle_table_response(_TableId, {replace_player, PlayerId, TableId, SeatNum}, ok = _Response,
  415. %% StateName,
  416. %% #state{reg_requests = RegRequests, seats = Seats,
  417. %% tables = Tables} = StateData) ->
  418. %% Seat = fetch_seat(TableId, SeatNum, Seats),
  419. %% NewSeats = store_seat(Seat#seat{registered_by_table = true}, Seats),
  420. %% %% Send response to a client for a delayed request
  421. %% NewRegRequests =
  422. %% case dict:find(PlayerId, RegRequests) of
  423. %% {ok, From} ->
  424. %% #table{relay = Relay, pid = TablePid} = fetch_table(TableId, Tables),
  425. %% gen_fsm:reply(From, {ok, {PlayerId, Relay, {?TAB_MOD, TablePid}}}),
  426. %% dict:erase(PlayerId, RegRequests);
  427. %% error -> RegRequests
  428. %% end,
  429. %% {next_state, StateName, StateData#state{seats = NewSeats,
  430. %% reg_requests = NewRegRequests}}.
  431. handle_table_response(TableId, RequestContext, Response, StateName,
  432. #state{game_id = GameId} = StateData) ->
  433. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled 'table response' received from table <~p> "
  434. "in state <~p>. Request context: ~p. Response: ~p.",
  435. [GameId, TableId, StateName, RequestContext, Response]),
  436. {next_state, StateName, StateData}.
  437. %%===================================================================
  438. handle_client_request({join, User}, From, StateName,
  439. #state{game_id = GameId, reg_requests = RegRequests,
  440. seats = Seats, players=Players, tables = Tables,
  441. table_module = TableModule} = StateData) ->
  442. #'PlayerInfo'{id = UserId, robot = _IsBot} = User,
  443. gas:info(?MODULE,"TRN_ELIMINATION <~p> The 'Join' request received from user: ~p.", [GameId, UserId]),
  444. if StateName == ?STATE_FINISHED ->
  445. gas:info(?MODULE,"TRN_ELIMINATION <~p> The tournament is finished. "
  446. "Reject to join user ~p.", [GameId, UserId]),
  447. {reply, {error, finished}, StateName, StateData};
  448. true ->
  449. case get_player_by_user_id(UserId, Players) of
  450. {ok, #player{status = active, id = PlayerId}} -> %% The user is an active member of the tournament.
  451. gas:info(?MODULE,"TRN_ELIMINATION <~p> User ~p is an active member of the tournament. "
  452. "Allow to join.", [GameId, UserId]),
  453. [#seat{table = TableId, registered_by_table = RegByTable}] = find_seats_by_player_id(PlayerId, Seats),
  454. case RegByTable of
  455. false -> %% Store this request to the waiting pool
  456. gas:info(?MODULE,"TRN_ELIMINATION <~p> User ~p not yet regirested by the table. "
  457. "Add the request to the waiting pool.", [GameId, UserId]),
  458. NewRegRequests = dict:store(PlayerId, From, RegRequests),
  459. {next_state, StateName, StateData#state{reg_requests = NewRegRequests}};
  460. _ ->
  461. gas:info(?MODULE,"TRN_ELIMINATION <~p> Return join response for player ~p immediately.",
  462. [GameId, UserId]),
  463. #table{relay = Relay, pid = TPid} = fetch_table(TableId, Tables),
  464. {reply, {ok, {PlayerId, Relay, {TableModule, TPid}}}, StateName, StateData}
  465. end;
  466. {ok, #player{status = eliminated}} ->
  467. gas:info(?MODULE,"TRN_ELIMINATION <~p> User ~p is member of the tournament but he was eliminated. "
  468. "Reject to join.", [GameId, UserId]),
  469. {reply, {error, out}, StateName, StateData};
  470. error -> %% Not a member
  471. gas:info(?MODULE,"TRN_ELIMINATION <~p> User ~p is not a member of the tournament. "
  472. "Reject to join.", [GameId, UserId]),
  473. {reply, {error, not_allowed}, StateName, StateData}
  474. end
  475. end;
  476. handle_client_request(Request, From, StateName, #state{game_id = GameId} = StateData) ->
  477. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled client request received from ~p in "
  478. "state <~p>: ~p.", [GameId, From, StateName, Request]),
  479. {reply, {error, unexpected_request}, StateName, StateData}.
  480. handle_system_request(last_tour_result, _From, StateName,
  481. #state{game_id = GameId, tournament_table = TTable,
  482. players = Players} = StateData) ->
  483. gas:info(?MODULE,"TRN_ELIMINATION <~p> Received request for the last tour results.", [GameId]),
  484. {LastTourNum, TourResultsRaw} = hd(lists:reverse(lists:keysort(1, TTable))),
  485. TourResults = [{get_user_id(PlayerId, Players), CommonPos, Points, Status}
  486. || {PlayerId, CommonPos, Points, Status} <- TourResultsRaw],
  487. {reply, {ok, {LastTourNum, TourResults}}, StateName, StateData};
  488. handle_system_request(Request, From, StateName, #state{game_id = GameId} = StateData) ->
  489. gas:info(?MODULE,"TRN_ELIMINATION <~p> Unhandled system request received from ~p in "
  490. "state <~p>: ~p.", [GameId, From, StateName, Request]),
  491. {reply, {error, unexpected_request}, StateName, StateData}.
  492. %%===================================================================
  493. init_tour(Tour, #state{game_id = GameId, tours_plan = Plan, tournament_table = TTable,
  494. table_params = TableParams, players = Players, quota_per_round = QuotaPerRound,
  495. table_id_counter = TableIdCounter, tables = OldTables, tours = Tours,
  496. players_per_table = PlayersPerTable, game_type = GameType,
  497. game_mode = GameMode, table_module = TableModule,
  498. rounds_per_tour = RoundsPerTour} = StateData) ->
  499. gas:info(?MODULE,"TRN_ELIMINATION <~p> Initializing tour <~p>...", [GameId, Tour]),
  500. PlayersList = prepare_players_for_new_tour(Tour, TTable, Plan, Players),
  501. PrepTTable = prepare_ttable_for_tables(TTable, Players),
  502. UsersIds = [UserId || {_, #'PlayerInfo'{id = UserId}, _} <- PlayersList],
  503. deduct_quota(GameId, GameType, GameMode, QuotaPerRound * RoundsPerTour, UsersIds),
  504. {NewTables, Seats, NewTableIdCounter, CrRequests} =
  505. setup_tables(TableModule, PlayersList, PlayersPerTable, PrepTTable, Tour,
  506. Tours, TableIdCounter, GameId, TableParams),
  507. if Tour > 1 -> finalize_tables_with_rejoin(TableModule, OldTables);
  508. true -> do_nothing
  509. end,
  510. gas:info(?MODULE,"TRN_ELIMINATION <~p> Initializing of tour <~p> is finished. "
  511. "Waiting creating confirmations from the tours' tables...",
  512. [GameId, Tour]),
  513. {next_state, ?STATE_WAITING_FOR_TABLES, StateData#state{tables = NewTables,
  514. seats = Seats,
  515. table_id_counter = NewTableIdCounter,
  516. tour = Tour,
  517. cr_tab_requests = CrRequests,
  518. reg_requests = dict:new(),
  519. tab_requests = dict:new(),
  520. tables_results = []
  521. }}.
  522. start_turn(#state{game_id = GameId, tour = Tour, tables = Tables,
  523. table_module = TableModule} = StateData) ->
  524. gas:info(?MODULE,"TRN_ELIMINATION <~p> Starting tour <~p>...", [GameId, Tour]),
  525. TablesList = tables_to_list(Tables),
  526. [send_to_table(TableModule, Pid, start_round) || #table{pid = Pid} <- TablesList],
  527. F = fun(Table, Acc) ->
  528. store_table(Table#table{state = ?TABLE_STATE_IN_PROGRESS}, Acc)
  529. end,
  530. NewTables = lists:foldl(F, Tables, TablesList),
  531. WL = [T#table.id || T <- TablesList],
  532. gas:info(?MODULE,"TRN_ELIMINATION <~p> Tour <~p> is started. Processing...",
  533. [GameId, Tour]),
  534. {next_state, ?STATE_TURN_PROCESSING, StateData#state{tables = NewTables,
  535. tables_wl = WL}}.
  536. process_tour_result(#state{game_id = GameId, tournament_table = TTable, tours = Tours,
  537. tours_plan = Plan, tour = Tour, tables_results = TablesResults,
  538. players = Players, tables = Tables, trn_id = TrnId,
  539. table_module = TableModule} = StateData) ->
  540. gas:info(?MODULE,"TRN_ELIMINATION <~p> Tour <~p> is completed. Starting results processing...", [GameId, Tour]),
  541. TourType = lists:nth(Tour, Plan),
  542. TourResult1 = case TourType of
  543. ne -> tour_result_all(TablesResults);
  544. {te, Limit} -> tour_result_per_table(Limit, TablesResults);
  545. {ce, Limit} -> tour_result_overall(Limit, TablesResults)
  546. end,
  547. TourResult = set_tour_results_position(TourResult1), %% [{PlayerId, CommonPos, Points, Status}]
  548. NewTTable = ttable_store_tour_result(Tour, TourResult, TTable),
  549. F = fun({PlayerId, _, _, eliminated}, Acc) -> set_player_status(PlayerId, eliminated, Acc);
  550. (_, Acc) -> Acc
  551. end,
  552. NewPlayers = lists:foldl(F, Players, TourResult),
  553. TourResultWithUserId = [{get_user_id(PlayerId, Players), Position, Points, Status}
  554. || {PlayerId, Position, Points, Status} <- TourResult],
  555. TablesResultsWithPos = set_tables_results_position(TablesResults, TourResult),
  556. [send_to_table(TableModule, TablePid, {tour_result, Tour, TourResultWithUserId})
  557. || #table{pid = TablePid} <- tables_to_list(Tables)],
  558. [send_to_table(TableModule, get_table_pid(TableId, Tables),
  559. {show_series_result, subs_status(TableResultWithPos, Tour, Plan)})
  560. || {TableId, TableResultWithPos} <- TablesResultsWithPos],
  561. TourResultWithStrUserId = [{user_id_to_string(UserId), Position, Points, Status}
  562. || {UserId, Position, Points, Status} <- TourResultWithUserId],
  563. wf:send(["system", "tournament_tour_note"], {TrnId, Tour, Tours, TourType, TourResultWithStrUserId}),
  564. {TRef, Magic} = start_timer(?SHOW_SERIES_RESULT_TIMEOUT),
  565. gas:info(?MODULE,"TRN_ELIMINATION <~p> Results processing of tour <~p> is finished. "
  566. "Waiting some time (~p secs) before continue...",
  567. [GameId, Tour, ?SHOW_SERIES_RESULT_TIMEOUT div 1000]),
  568. {next_state, ?STATE_SHOW_SERIES_RESULT, StateData#state{timer = TRef, timer_magic = Magic,
  569. tournament_table = NewTTable,
  570. players = NewPlayers}}.
  571. finalize_tournament(#state{game_id = GameId, awards = Awards, tournament_table = TTable,
  572. players = Players, trn_id = TrnId} = StateData) ->
  573. gas:info(?MODULE,"TRN_ELIMINATION <~p> Finalizing the tournament...", [GameId]),
  574. AwardsDistrib = awards_distribution(TTable, Awards),
  575. AwardsDistribUserId = [{user_id_to_string(get_user_id(PlayerId, Players)), Pos, GiftId}
  576. || {PlayerId, Pos, GiftId} <- AwardsDistrib],
  577. [wf:send(["gifts", "user", UserId, "give_gift"], {GiftId})
  578. || {UserId, _Pos, GiftId} <- AwardsDistribUserId],
  579. %% TODO: Do we need advertise the prizes to game clients?
  580. gas:info(?MODULE,"TRN_ELIMINATION <~p> Awards distribution: ~p", [GameId, AwardsDistribUserId]),
  581. wf:send(["system", "tournament_ends_note"], {TrnId, AwardsDistribUserId}),
  582. {TRef, Magic} = start_timer(?SHOW_TOURNAMENT_RESULT_TIMEOUT),
  583. gas:info(?MODULE,"TRN_ELIMINATION <~p> The tournament is finalized. "
  584. "Waiting some time (~p secs) before continue...",
  585. [GameId, ?SHOW_TOURNAMENT_RESULT_TIMEOUT div 1000]),
  586. {next_state, ?STATE_FINISHED, StateData#state{timer = TRef, timer_magic = Magic}}.
  587. %% awards_distribution(TTable, Awards) -> [{PlayerId, Pos, GiftId}]
  588. awards_distribution(TTable, Awards) ->
  589. MTTable = merge_tournament_table(TTable),
  590. F = fun({PlayerId, Pos}, Acc) ->
  591. try lists:nth(Pos, Awards) of
  592. undefined -> Acc;
  593. GiftId -> [{PlayerId, Pos, GiftId} | Acc]
  594. catch
  595. _:_ -> Acc
  596. end
  597. end,
  598. lists:foldl(F, [], MTTable).
  599. %% merge_tournament_table(TTable) -> [{PlayerId, Pos}]
  600. merge_tournament_table(TTable) ->
  601. [{_, Tour0} | Tours] = lists:keysort(1, TTable),
  602. PlayersPos0 = tour_res_to_players_pos(Tour0),
  603. F = fun({_, Tour}, Acc) ->
  604. lists:foldl(fun({PlayerId, Pos}, Acc2) ->
  605. lists:keyreplace(PlayerId, 1, Acc2, {PlayerId, Pos})
  606. end, Acc, tour_res_to_players_pos(Tour))
  607. end,
  608. lists:foldl(F, PlayersPos0, Tours).
  609. %% tour_res_to_players_pos(TourRes) -> [{PlayerId, Pos}]
  610. tour_res_to_players_pos(TourRes) ->
  611. [{PlayerId, Pos} || {PlayerId, Pos, _Points, _Status} <- TourRes].
  612. deduct_quota(GameId, GameType, GameMode, Amount, UsersIds) ->
  613. TI = #ti_game_event{game_name = GameType, game_mode = GameMode,
  614. id = GameId, double_points = 1,
  615. type = start_tour, tournament_type = ?TOURNAMENT_TYPE},
  616. [
  617. % nsm_accounts:transaction(binary_to_list(UserId), ?CURRENCY_QUOTA, -Amount, TI)
  618. kvs:add(#transaction{
  619. id=kvs:next_id(transaction,1),
  620. feed_id={quota,binary_to_list(UserId)},
  621. comment=TI})
  622. || UserId <- UsersIds],
  623. ok.
  624. tour_result_all(TablesResults) ->
  625. F = fun({_, TableRes}, Acc) ->
  626. [{Pl, Points, active} || {Pl, Points} <- TableRes] ++ Acc
  627. end,
  628. lists:foldl(F, [], TablesResults).
  629. tour_result_per_table(NextTourLimit, TablesResults) ->
  630. F = fun({_, TableResult}, Acc) ->
  631. SortedRes = sort_results(TableResult),
  632. {Winners, _} = lists:unzip(lists:sublist(SortedRes, NextTourLimit)),
  633. [case lists:member(Pl, Winners) of
  634. true -> {Pl, Points, active};
  635. false -> {Pl, Points, eliminated}
  636. end || {Pl, Points} <- TableResult] ++ Acc
  637. end,
  638. lists:foldl(F, [], TablesResults).
  639. tour_result_overall(TourLimit, TablesResults) ->
  640. F = fun({_, TableRes}, Acc) -> TableRes ++ Acc end,
  641. OverallResults = lists:foldl(F, [], TablesResults),
  642. SortedResults = sort_results(OverallResults),
  643. {Winners, _} = lists:unzip(lists:sublist(SortedResults, TourLimit)),
  644. [case lists:member(Pl, Winners) of
  645. true -> {Pl, Points, active};
  646. false -> {Pl, Points, eliminated}
  647. end || {Pl, Points} <- OverallResults].
  648. %% set_tour_results_position([{PlayerId, Points, Status}]) -> [{PlayerId, Pos, Points, Status}]
  649. set_tour_results_position(TourResult) ->
  650. F = fun({PlayerId, Points, Status}, Pos) ->
  651. {{PlayerId, Pos, Points, Status}, Pos + 1}
  652. end,
  653. {TourResultsWithPos, _} = lists:mapfoldl(F, 1, sort_results2(TourResult)),
  654. TourResultsWithPos.
  655. %% set_tables_results_position/2 -> [{TableId, [{PlayerId, Position, Points, Status}]}]
  656. set_tables_results_position(TablesResults, TurnResult) ->
  657. [begin
  658. TabResWithStatus = [lists:keyfind(PlayerId, 1, TurnResult) || {PlayerId, _} <- TableResult],
  659. {TableId, set_table_results_position(TabResWithStatus)}
  660. end || {TableId, TableResult} <- TablesResults].
  661. %% set_table_results_position([{PlayerId, CommonPos, Points, Status}]) -> [{PlayerId, TabPos, Points, Status}]
  662. set_table_results_position(TableResult) ->
  663. F = fun({PlayerId, _, Points, Status}, Pos) ->
  664. {{PlayerId, Pos, Points, Status}, Pos + 1}
  665. end,
  666. {TurnResultsWithPos, _} = lists:mapfoldl(F, 1, lists:keysort(2, TableResult)),
  667. TurnResultsWithPos.
  668. subs_status(TableResultWithPos, Turn, Plan) ->
  669. LastTurn = Turn == length(Plan),
  670. {ActSubst, ElimSubst} = if LastTurn -> {winner, eliminated};
  671. true -> {none, eliminated}
  672. end,
  673. [case Status of
  674. active -> {PlayerId, Pos, Points, ActSubst};
  675. eliminated -> {PlayerId, Pos, Points, ElimSubst}
  676. end || {PlayerId, Pos, Points, Status} <- TableResultWithPos].
  677. %% sort_results(Results) -> SortedResults
  678. %% Types: Results = SortedResults = [{PlayerId, Points}]
  679. %% Description: Sort the list from a best result to a lower one.
  680. sort_results(Results) ->
  681. SF = fun({PId1, Points}, {PId2, Points}) -> PId1 =< PId2;
  682. ({_, Points1}, {_, Points2}) -> Points2 =< Points1
  683. end,
  684. lists:sort(SF, Results).
  685. %% sort_results2(Results) -> SortedResults
  686. %% Types: Results = SortedResults = [{PlayerId, Points, Status}] Status = active | eliminated
  687. sort_results2(Results) ->
  688. SF = fun({PId1, Points, Status}, {PId2, Points, Status}) -> PId1 =< PId2;
  689. ({_PId1, Points1, Status}, {_PId2, Points2, Status}) -> Points2 =< Points1;
  690. ({_PId1, _Points1, _Status1}, {_PId2, _Points2, Status2}) -> Status2 == eliminated
  691. end,
  692. lists:sort(SF, Results).
  693. %% replace_player_by_bot(PlayerId, TableId, SeatNum,
  694. %% #state{players = Players, seats = Seats,
  695. %% game_id = GameId, bots_params = BotsParams,
  696. %% player_id_counter = NewPlayerId, tables = Tables,
  697. %% tab_requests = Requests} = StateData) ->
  698. %% NewPlayers = del_player(PlayerId, Players),
  699. %% [#'PlayerInfo'{id = UserId} = UserInfo] = spawn_bots(GameId, BotsParams, 1),
  700. %% NewPlayers2 = reg_player(#player{id = NewPlayerId, user_id = UserId, is_bot = true}, NewPlayers),
  701. %% NewSeats = assign_seat(TableId, SeatNum, NewPlayerId, true, false, false, Seats),
  702. %% TablePid = get_table_pid(TableId, Tables),
  703. %% NewRequests = table_req_replace_player(TablePid, NewPlayerId, UserInfo, TableId, SeatNum, Requests),
  704. %% {next_state, ?STATE_PROCESSING, StateData#state{players = NewPlayers2,
  705. %% seats = NewSeats,
  706. %% player_id_counter = NewPlayerId + 1,
  707. %% tab_requests = NewRequests}}.
  708. %%
  709. %% table_req_replace_player(TablePid, PlayerId, UserInfo, TableId, SeatNum, TabRequests) ->
  710. %% RequestId = make_ref(),
  711. %% NewRequests = dict:store(RequestId, {replace_player, PlayerId, TableId, SeatNum}, TabRequests),
  712. %% send_to_table(TablePid, {replace_player, RequestId, UserInfo, PlayerId, SeatNum}),
  713. %% NewRequests.
  714. %% prepare_players_for_new_tour(Tour, TTable, ToursPlan, Players) -> [{PlayerId, UserInfo, Points}]
  715. prepare_players_for_new_tour(Tour, TTable, ToursPlan, Players) ->
  716. PrevTour = Tour - 1,
  717. TResult = ttable_get_tour_result(PrevTour, TTable),
  718. if Tour == 1 ->
  719. [{PlayerId, get_user_info(PlayerId, Players), _Points = 0}
  720. || {PlayerId, _, _, active} <- TResult];
  721. true ->
  722. case lists:nth(PrevTour, ToursPlan) of
  723. ne -> %% No one was eliminated => using the prev turn points
  724. [{PlayerId, get_user_info(PlayerId, Players), Points}
  725. || {PlayerId, _, Points, active} <- TResult];
  726. _ ->
  727. [{PlayerId, get_user_info(PlayerId, Players), _Points = 0}
  728. || {PlayerId, _, _, active} <- TResult]
  729. end
  730. end.
  731. prepare_ttable_for_tables(TTable, Players) ->
  732. [{Tour, [{get_user_id(PlayerId, Players), Place, Score, Status}
  733. || {PlayerId, Place, Score, Status} <- Results]}
  734. || {Tour, Results} <- TTable].
  735. %% setup_tables(TableModule, Players, PlayersPerTable, TTable, Tour, Tours, TableIdCounter, GameId, TableParams) ->
  736. %% {Tables, Seats, NewTableIdCounter, CrRequests}
  737. %% Types: Players = {PlayerId, UserInfo, Points}
  738. %% TTable = [{Tour, [{UserId, CommonPos, Score, Status}]}]
  739. setup_tables(TableModule, Players, PlayersPerTable, TTable, Tour,
  740. Tours, TableIdCounter, GameId, TableParams) ->
  741. SPlayers = shuffle(Players),
  742. Groups = split_by_num(PlayersPerTable, SPlayers),
  743. F = fun(Group, {TAcc, SAcc, TableId, TCrRequestsAcc}) ->
  744. {TPlayers, _} = lists:mapfoldl(fun({PlayerId, UserInfo, Points}, SeatNum) ->
  745. {{PlayerId, UserInfo, SeatNum, Points}, SeatNum+1}
  746. end, 1, Group),
  747. TableParams2 = [{players, TPlayers}, {ttable, TTable}, {tour, Tour},
  748. {tours, Tours}, {parent, {?MODULE, self()}} | TableParams],
  749. {ok, TabPid} = spawn_table(TableModule, GameId, TableId, TableParams2),
  750. MonRef = erlang:monitor(process, TabPid),
  751. NewTAcc = reg_table(TableId, TabPid, MonRef, _GlTableId = 0, _Context = undefined, TAcc),
  752. F2 = fun({PlId, _, SNum, _}, Acc) ->
  753. assign_seat(TableId, SNum, PlId, _Reg = false, _Conn = false, Acc)
  754. end,
  755. NewSAcc = lists:foldl(F2, SAcc, TPlayers),
  756. PlayersIds = [PlayerId || {PlayerId, _, _} <- Group],
  757. NewTCrRequestsAcc = dict:store(TableId, PlayersIds, TCrRequestsAcc),
  758. {NewTAcc, NewSAcc, TableId + 1, NewTCrRequestsAcc}
  759. end,
  760. lists:foldl(F, {tables_init(), seats_init(), TableIdCounter, dict:new()}, Groups).
  761. %% setup_players(Registrants) -> Players
  762. setup_players(Registrants) ->
  763. F = fun(UserId, {Acc, PlayerId}) ->
  764. UserInfo = auth_server:get_user_info_by_user_id(UserId),
  765. NewAcc = store_player(#player{id = PlayerId, user_id = UserId,
  766. user_info = UserInfo, status = active}, Acc),
  767. {NewAcc, PlayerId + 1}
  768. end,
  769. {Players, _} = lists:foldl(F, {players_init(), 1}, Registrants),
  770. Players.
  771. %% finalize_tables_with_rejoin(TableModule, Tables) -> ok
  772. finalize_tables_with_rejoin(TableModule, Tables) ->
  773. F = fun(#table{mon_ref = MonRef, pid = TablePid}) ->
  774. erlang:demonitor(MonRef, [flush]),
  775. send_to_table(TableModule, TablePid, rejoin_players),
  776. send_to_table(TableModule, TablePid, stop)
  777. end,
  778. lists:foreach(F, tables_to_list(Tables)).
  779. %% finalize_tables_with_rejoin(TableModule, Tables) -> ok
  780. finalize_tables_with_disconnect(TableModule, Tables) ->
  781. F = fun(#table{mon_ref = MonRef, pid = TablePid}) ->
  782. erlang:demonitor(MonRef, [flush]),
  783. send_to_table(TableModule, TablePid, disconnect_players),
  784. send_to_table(TableModule, TablePid, stop)
  785. end,
  786. lists:foreach(F, tables_to_list(Tables)).
  787. %% ttable_init(PlayersIds) -> TTable
  788. %% Types: TTable = [{Tour, TourResult}]
  789. ttable_init(PlayersIds) -> [{0, [{Id, Id, 0, active} || Id <- PlayersIds]}].
  790. %% ttable_get_tour_result(Tour, TTable) -> undefined | TourResult
  791. %% Types: TourResult = [{PlayerId, CommonPos, Points, PlayerState}]
  792. %% PlayerState = undefined | active | eliminated
  793. ttable_get_tour_result(Tour, TTable) ->
  794. proplists:get_value(Tour, TTable).
  795. %% ttable_store_tour_result(Tour, TourResult, TTable) -> NewTTable
  796. ttable_store_tour_result(Tour, TourResult, TTable) ->
  797. lists:keystore(Tour, 1, TTable, {Tour, TourResult}).
  798. %% players_init() -> players()
  799. players_init() -> midict:new().
  800. %% store_player(#player{}, Players) -> NewPlayers
  801. store_player(#player{id =Id, user_id = UserId} = Player, Players) ->
  802. midict:store(Id, Player, [{user_id, UserId}], Players).
  803. get_players_ids(Players) ->
  804. [P#player.id || P <- players_to_list(Players)].
  805. get_player_by_user_id(UserId, Players) ->
  806. case midict:geti(UserId, user_id, Players) of
  807. [Player] -> {ok, Player};
  808. [] -> error
  809. end.
  810. %% players_to_list(Players) -> List
  811. players_to_list(Players) -> midict:all_values(Players).
  812. get_user_info(PlayerId, Players) ->
  813. #player{user_info = UserInfo} = midict:fetch(PlayerId, Players),
  814. UserInfo.
  815. get_user_id(PlayerId, Players) ->
  816. #player{user_id = UserId} = midict:fetch(PlayerId, Players),
  817. UserId.
  818. set_player_status(PlayerId, Status, Players) ->
  819. Player = midict:fetch(PlayerId, Players),
  820. store_player(Player#player{status = Status}, Players).
  821. tables_init() -> midict:new().
  822. reg_table(TableId, Pid, MonRef, GlobalId, TableContext, Tables) ->
  823. Table = #table{id = TableId, pid = Pid, mon_ref = MonRef, global_id = GlobalId,
  824. state = initializing, context = TableContext},
  825. store_table(Table, Tables).
  826. update_created_table(TableId, Relay, Tables) ->
  827. Table = midict:fetch(TableId, Tables),
  828. NewTable = Table#table{relay = Relay, state = ?TABLE_STATE_READY},
  829. store_table(NewTable, Tables).
  830. store_table(#table{id = TableId, pid = Pid, mon_ref = MonRef, global_id = GlobalId} = Table, Tables) ->
  831. midict:store(TableId, Table, [{pid, Pid}, {global_id, GlobalId}, {mon_ref, MonRef}], Tables).
  832. fetch_table(TableId, Tables) -> midict:fetch(TableId, Tables).
  833. get_table_pid(TabId, Tables) ->
  834. #table{pid = TabPid} = midict:fetch(TabId, Tables),
  835. TabPid.
  836. get_table_by_mon_ref(MonRef, Tables) ->
  837. case midict:geti(MonRef, mon_ref, Tables) of
  838. [Table] -> Table;
  839. [] -> not_found
  840. end.
  841. tables_to_list(Tables) -> midict:all_values(Tables).
  842. seats_init() -> midict:new().
  843. find_seats_by_player_id(PlayerId, Seats) ->
  844. midict:geti(PlayerId, player_id, Seats).
  845. find_seats_by_table_id(TabId, Seats) ->
  846. midict:geti(TabId, table_id, Seats).
  847. %% assign_seat(TabId, SeatNum, PlayerId, RegByTable, Connected, Seats) -> NewSeats
  848. %% PlayerId = integer()
  849. %% RegByTable = Connected = undefined | boolean()
  850. assign_seat(TabId, SeatNum, PlayerId, RegByTable, Connected, Seats) ->
  851. Seat = #seat{table = TabId, seat_num = SeatNum, player_id = PlayerId,
  852. registered_by_table = RegByTable, connected = Connected},
  853. store_seat(Seat, Seats).
  854. update_seat_connect_status(TableId, SeatNum, ConnStatus, Seats) ->
  855. Seat = midict:fetch({TableId, SeatNum}, Seats),
  856. NewSeat = Seat#seat{connected = ConnStatus},
  857. store_seat(NewSeat, Seats).
  858. store_seat(#seat{table = TabId, seat_num = SeatNum, player_id = PlayerId,
  859. registered_by_table = _RegByTable,
  860. connected = Connected} = Seat, Seats) ->
  861. Indices = if PlayerId == undefined ->
  862. [{table_id, TabId}, {free, true}, {free_at_tab, TabId}];
  863. true ->
  864. [{table_id, TabId}, {free, false}, {non_free_at_tab, TabId},
  865. {player_id, PlayerId}, {{connected, TabId}, Connected}]
  866. end,
  867. midict:store({TabId, SeatNum}, Seat, Indices, Seats).
  868. user_id_to_string(UserId) -> binary_to_list(UserId).
  869. shuffle(List) -> deck:to_list(deck:shuffle(deck:from_list(List))).
  870. split_by_num(Num, List) -> split_by_num(Num, List, []).
  871. split_by_num(_, [], Acc) -> lists:reverse(Acc);
  872. split_by_num(Num, List, Acc) ->
  873. {Group, Rest} = lists:split(Num, List),
  874. split_by_num(Num, Rest, [Group | Acc]).
  875. %% start_timer(Timeout) -> {TRef, Magic}
  876. start_timer(Timeout) ->
  877. Magic = make_ref(),
  878. TRef = erlang:send_after(Timeout, self(), {timeout, Magic}),
  879. {TRef, Magic}.
  880. spawn_table(TableModule, GameId, TableId, Params) -> TableModule:start(GameId, TableId, Params).
  881. send_to_table(TableModule, TabPid, Message) -> TableModule:parent_message(TabPid, Message).
  882. %% table_parameters(ParentMod, ParentPid, Speed) -> Proplist
  883. %% table_parameters(ParentMod, ParentPid, Speed, GameType, Tours) ->
  884. %% [
  885. %% {parent, {ParentMod, ParentPid}},
  886. %% {seats_num, 4},
  887. %% %% {players, []},
  888. %% {table_name, ""},
  889. %% {mult_factor, 1},
  890. %% {slang_allowed, false},
  891. %% {observers_allowed, false},
  892. %% {tournament_type, elimination},
  893. %% {turn_timeout, get_timeout(turn, Speed)},
  894. %% {reveal_confirmation_timeout, get_timeout(reveal_confirmation, Speed)},
  895. %% {ready_timeout, get_timeout(ready, Speed)},
  896. %% {round_timeout, get_timeout(round, Speed)},
  897. %% {set_timeout, get_timeout(tour, Tours)},
  898. %% {speed, Speed},
  899. %% {game_type, GameType},
  900. %% {rounds, ?ROUNDS_PER_TOUR},
  901. %% {reveal_confirmation, true},
  902. %% {next_series_confirmation, no},
  903. %% {pause_mode, disabled},
  904. %% {social_actions_enabled, false}
  905. %% ].
  906. get_param(ParamId, Params) ->
  907. {_, Value} = lists:keyfind(ParamId, 1, Params),
  908. Value.
  909. get_option(OptionId, Params, DefValue) ->
  910. proplists:get_value(OptionId, Params, DefValue).