nsg_trn_elimination.erl 51 KB

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