nsg_trn_lucky.erl 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. %%% -------------------------------------------------------------------
  2. %%% Author : Sergii Polkovnikov <serge.polkovnikov@gmail.com>
  3. %%% Description : The "Quick play" logic
  4. %%%
  5. %%% Created : Oct 16, 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_lucky).
  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. %% --------------------------------------------------------------------
  25. %% External exports
  26. -export([start/1, start/2, start_link/2, reg/2]).
  27. %% gen_fsm callbacks
  28. -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
  29. -export([table_message/3, client_message/2, client_request/2, client_request/3]).
  30. -record(state,
  31. {%% Static values
  32. game_id :: pos_integer(),
  33. game :: atom(),
  34. game_mode :: atom(),
  35. table_params :: proplists:proplist(),
  36. table_module :: atom(),
  37. bot_module :: atom(),
  38. seats_per_table :: integer(),
  39. game_name :: string(),
  40. mode :: normal | exclusive,
  41. %% Dynamic values
  42. players, %% The register of tournament players
  43. tables, %% The register of tournament tables
  44. seats, %% Stores relation between players and tables seats
  45. player_id_counter :: pos_integer(),
  46. table_id_counter :: pos_integer(),
  47. cr_tab_requests :: dict(), %% {TableId, PlayersIds}
  48. reg_requests :: dict(), %% {PlayerId, From}
  49. tab_requests :: dict() %% {RequestId, RequestContext}
  50. }).
  51. -record(player,
  52. {
  53. id :: pos_integer(),
  54. user_id,
  55. is_bot :: boolean()
  56. }).
  57. -record(table,
  58. {
  59. id :: pos_integer(),
  60. global_id :: pos_integer(),
  61. pid,
  62. relay :: {atom(), pid()}, %%{RelayMod, RelayPid}
  63. mon_ref,
  64. state :: initializing | ready | in_progress | finished,
  65. scoring_state,
  66. timer :: reference()
  67. }).
  68. -record(seat,
  69. {
  70. table :: pos_integer(),
  71. seat_num :: integer(),
  72. player_id :: undefined | pos_integer(),
  73. is_bot :: undefined | boolean(),
  74. registered_by_table :: undefined | boolean(),
  75. connected :: undefined | boolean()
  76. }).
  77. -define(STATE_INIT, state_init).
  78. -define(STATE_PROCESSING, state_processing).
  79. -define(TABLE_STATE_INITIALIZING, initializing).
  80. -define(TABLE_STATE_READY, ready).
  81. -define(TABLE_STATE_IN_PROGRESS, in_progress).
  82. -define(TABLE_STATE_FINISHED, finished).
  83. -define(REST_TIMEOUT, 5000). %% Time between game finsh and start of new round
  84. %% ====================================================================
  85. %% External functions
  86. %% ====================================================================
  87. start([GameId, Params]) -> %% XXX WTF?
  88. ?INFO(" +++ START LUCKY"),
  89. start(GameId,Params).
  90. start(GameId, Params) ->
  91. gen_fsm:start(?MODULE, [GameId, Params, self()], []).
  92. start_link(GameId, Params) ->
  93. gen_fsm:start_link(?MODULE, [GameId, Params, self()], []).
  94. reg(Pid, User) ->
  95. client_request(Pid, {reg, User}, 10000).
  96. table_message(Pid, TableId, Message) ->
  97. gen_fsm:send_all_state_event(Pid, {table_message, TableId, Message}).
  98. client_message(Pid, Message) ->
  99. gen_fsm:send_all_state_event(Pid, {client_message, Message}).
  100. client_request(Pid, Message) ->
  101. client_request(Pid, Message, 5000).
  102. client_request(Pid, Message, Timeout) ->
  103. gen_fsm:sync_send_all_state_event(Pid, {client_request, Message}, Timeout).
  104. %% ====================================================================
  105. %% Server functions
  106. %% ====================================================================
  107. init([GameId, Params, _Manager]) ->
  108. SeatsPerTable = get_param(seats, Params),
  109. Game = get_param(game, Params),
  110. GameMode = get_param(game_mode, Params),
  111. GameName = get_param(game_name, Params),
  112. %%% XXX QuotaPerRound = get_param(quota_per_round, Params),
  113. TableParams = get_param(table_params, Params),
  114. TableModule = get_param(table_module, Params),
  115. BotModule = get_param(bot_module, Params),
  116. ?INFO("TRN_LUCKY <~p> All parameteres are read. Send the directive to start the game.", [GameId]),
  117. gen_fsm:send_all_state_event(self(), go),
  118. {ok, ?STATE_INIT,
  119. #state{game_id = GameId,
  120. game = Game,
  121. game_mode = GameMode,
  122. game_name = GameName,
  123. seats_per_table = SeatsPerTable,
  124. table_params = [{parent, {?MODULE, self()}} | TableParams],
  125. table_module = TableModule,
  126. bot_module = BotModule
  127. }}.
  128. %%===================================================================
  129. handle_event(go, ?STATE_INIT, #state{game_id = GameId, game = Game, game_mode = GameMode,
  130. game_name = GameName} = StateData) ->
  131. ?INFO("TRN_LUCKY <~p> Received the directive to start the game.", [GameId]),
  132. DeclRec = #game_table{id = GameId,
  133. game_type = Game,
  134. game_mode = GameMode,
  135. game_process = self(),
  136. game_module = ?MODULE,
  137. name = GameName,
  138. age_limit = 100,
  139. game_speed = undefined,
  140. feel_lucky = true,
  141. owner = undefined,
  142. creator = undefined,
  143. rounds = undefined,
  144. pointing_rules = [],
  145. pointing_rules_ex = [],
  146. users = []
  147. },
  148. gproc:reg({p,l,self()}, DeclRec),
  149. {next_state, ?STATE_PROCESSING,
  150. StateData#state{players = players_init(),
  151. tables = tables_init(),
  152. seats = seats_init(),
  153. player_id_counter = 1,
  154. table_id_counter = 1,
  155. cr_tab_requests = dict:new(),
  156. reg_requests = dict:new(),
  157. tab_requests = dict:new()
  158. }};
  159. handle_event({client_message, Message}, StateName, StateData) ->
  160. handle_client_message(Message, StateName, StateData);
  161. handle_event({table_message, TableId, Message}, StateName, StateData) ->
  162. handle_table_message(TableId, Message, StateName, StateData);
  163. handle_event(_Event, StateName, StateData) ->
  164. {next_state, StateName, StateData}.
  165. handle_sync_event({client_request, Request}, From, StateName, StateData) ->
  166. handle_client_request(Request, From, StateName, StateData);
  167. handle_sync_event(_Event, _From, StateName, StateData) ->
  168. Reply = ok,
  169. {reply, Reply, StateName, StateData}.
  170. %%===================================================================
  171. handle_info({'DOWN', MonRef, process, _Pid, _}, StateName,
  172. #state{game_id = GameId, tables = Tables,
  173. seats = Seats, players = Players} = StateData) ->
  174. case get_table_by_mon_ref(MonRef, Tables) of
  175. #table{id = TabId, timer = TRef} ->
  176. ?INFO("TRN_LUCKY <~p> Table <~p> is down. Cleaning up registeres.", [GameId, TabId]),
  177. case TRef == undefined of false -> erlang:cancel_timer(TRef); true -> skip end,
  178. PlayersIds =
  179. [PlayerId || #seat{player_id = PlayerId} <- find_seats_with_players_for_table_id(TabId, Seats)],
  180. NewTables = del_table(TabId, Tables),
  181. NewSeats = del_seats_by_table_id(TabId, Seats),
  182. NewPlayers = del_players(PlayersIds, Players),
  183. {next_state, StateName, StateData#state{tables = NewTables,
  184. seats = NewSeats,
  185. players = NewPlayers}};
  186. not_found ->
  187. {next_state, StateName, StateData}
  188. end;
  189. handle_info({rest_timeout, TableId}, StateName,
  190. #state{game_id = GameId, tables = Tables, table_module = TableModule} = StateData) ->
  191. ?INFO("TRN_LUCKY <~p> Time to start new round for table <~p>.", [GameId, TableId]),
  192. case get_table(TableId, Tables) of
  193. {ok, #table{pid = TablePid}} ->
  194. ?INFO("TRN_LUCKY <~p> Initiating new round at table <~p>.", [GameId, TableId]),
  195. NewTables = set_table_state(TableId, ?TABLE_STATE_IN_PROGRESS, Tables),
  196. send_to_table(TableModule, TablePid, start_round),
  197. {next_state, StateName, StateData#state{tables = NewTables}};
  198. error -> %% If no such table ignore the timeout
  199. ?INFO("TRN_LUCKY <~p> There is no table <~p>. Can't start new round for it.", [GameId, TableId]),
  200. {next_state, StateName, StateData}
  201. end;
  202. handle_info(Message, StateName, #state{game_id = GameId} = StateData) ->
  203. ?INFO("TRN_STANDALONE <~p> Unhandled message(info) received in state <~p>: ~p.",
  204. [GameId, StateName, Message]),
  205. {next_state, StateName, StateData}.
  206. %%===================================================================
  207. terminate(_Reason, _StateName, #state{game_id=GameId}=_StatData) ->
  208. ?INFO("TRN_LUCKY <~p> Shutting down at state: <~p>. Reason: ~p",
  209. [GameId, _StateName, _Reason]),
  210. ok.
  211. %%===================================================================
  212. code_change(_OldVsn, StateName, StateData, _Extra) ->
  213. {ok, StateName, StateData}.
  214. %% --------------------------------------------------------------------
  215. %%% Internal functions
  216. %% --------------------------------------------------------------------
  217. handle_client_message(_Msg, StateName, StateData) ->
  218. {next_state, StateName, StateData}.
  219. %%===================================================================
  220. %% handle_table_message(TableId, Message, StateName, StateData)
  221. handle_table_message(TableId, {player_connected, PlayerId},
  222. ?STATE_PROCESSING,
  223. #state{game_id = GameId, seats = Seats, seats_per_table = SeatsNum,
  224. tables = Tables, table_module = TableModule} = StateData)
  225. when is_integer(TableId), is_integer(PlayerId) ->
  226. ?INFO("TRN_LUCKY <~p> The player_connected notification received from "
  227. "table <~p>. PlayerId: <~p>", [GameId, TableId, PlayerId]),
  228. case find_seats_by_player_id(PlayerId, Seats) of
  229. [#seat{seat_num = SeatNum}] ->
  230. NewSeats = update_seat_connect_status(TableId, SeatNum, true, Seats),
  231. case fetch_table(TableId, Tables) of
  232. #table{state = ?TABLE_STATE_READY, pid = TabPid} ->
  233. case is_all_players_connected(TableId, SeatsNum, NewSeats) of
  234. true ->
  235. ?INFO("TRN_LUCKY <~p> All clients connected. Starting a game.",
  236. [GameId]),
  237. NewTables = set_table_state(TableId, ?TABLE_STATE_IN_PROGRESS, Tables),
  238. send_to_table(TableModule, TabPid, start_round),
  239. {next_state, ?STATE_PROCESSING, StateData#state{seats = NewSeats,
  240. tables = NewTables}};
  241. false ->
  242. {next_state, ?STATE_PROCESSING, StateData#state{seats = NewSeats}}
  243. end;
  244. _ ->
  245. {next_state, ?STATE_PROCESSING, StateData#state{seats = NewSeats}}
  246. end;
  247. [] -> %% Ignoring the message
  248. {next_state, ?STATE_PROCESSING, StateData}
  249. end;
  250. handle_table_message(TableId, {player_disconnected, PlayerId},
  251. ?STATE_PROCESSING, #state{game_id = GameId, seats = Seats} = StateData)
  252. when is_integer(TableId), is_integer(PlayerId) ->
  253. ?INFO("TRN_LUCKY <~p> The player_disconnected notification received from "
  254. "table <~p>. PlayerId: <~p>", [GameId, TableId, PlayerId]),
  255. case find_seats_by_player_id(PlayerId, Seats) of
  256. [#seat{seat_num = SeatNum, is_bot = IsBot}] ->
  257. case real_players_at_table(TableId, Seats) of
  258. 1 when not IsBot -> %% Last real player gone
  259. ?INFO("TRN_LUCKY <~p> Last real player gone from "
  260. "table <~p>. Closing the table.", [GameId, TableId]),
  261. unreg_player_and_eliminate_table(PlayerId, TableId, StateData);
  262. _ ->
  263. ?INFO("TRN_LUCKY <~p> Al least one real player is at table <~p>. "
  264. "Starting a bot to replace free seat.", [GameId, TableId]),
  265. replace_player_by_bot(PlayerId, TableId, SeatNum, StateData)
  266. end;
  267. [] -> %% Ignoring the message
  268. {next_state, ?STATE_PROCESSING, StateData}
  269. end;
  270. handle_table_message(TableId, {table_created, Relay}, ?STATE_PROCESSING,
  271. #state{game_id = GameId, tables = Tables, seats = Seats,
  272. cr_tab_requests = TCrRequests, table_module = TableModule,
  273. reg_requests = RegRequests} = StateData)
  274. when is_integer(TableId) ->
  275. ?INFO("TRN_LUCKY <~p> The <table_created> notification received from table: ~p.",
  276. [GameId, TableId]),
  277. TabInitPlayers = dict:fetch(TableId, TCrRequests),
  278. %% Update status of players
  279. TabSeats = find_seats_by_table_id(TableId, Seats),
  280. F = fun(#seat{player_id = PlayerId} = S, Acc) ->
  281. case lists:member(PlayerId, TabInitPlayers) of
  282. true -> store_seat(S#seat{registered_by_table = true}, Acc);
  283. false -> Acc
  284. end
  285. end,
  286. NewSeats = lists:foldl(F, Seats, TabSeats),
  287. %% Process delayed registration requests
  288. TablePid = get_table_pid(TableId, Tables),
  289. F2 = fun(PlayerId, Acc) ->
  290. case dict:find(PlayerId, Acc) of
  291. {ok, From} ->
  292. gen_fsm:reply(From, {ok, {PlayerId, Relay, {TableModule, TablePid}}}),
  293. dict:erase(PlayerId, Acc);
  294. error -> Acc
  295. end
  296. end,
  297. NewRegRequests = lists:foldl(F2, RegRequests, TabInitPlayers),
  298. NewTCrRequests = dict:erase(TableId, TCrRequests),
  299. NewTables = update_created_table(TableId, Relay, Tables),
  300. {next_state, ?STATE_PROCESSING, StateData#state{tables = NewTables,
  301. seats = NewSeats,
  302. cr_tab_requests = NewTCrRequests,
  303. reg_requests = NewRegRequests}};
  304. handle_table_message(TableId, {round_finished, NewScoringState, _RoundScore, _TotalScore},
  305. ?STATE_PROCESSING,
  306. #state{game_id = GameId, tables = Tables, table_module = TableModule
  307. } = StateData)
  308. when is_integer(TableId) ->
  309. ?INFO("TRN_LUCKY <~p> The <round_finished> notification received from table: ~p.",
  310. [GameId, TableId]),
  311. #table{pid = TablePid} = Table = fetch_table(TableId, Tables),
  312. TRef = erlang:send_after(?REST_TIMEOUT, self(), {rest_timeout, TableId}),
  313. NewTable = Table#table{scoring_state = NewScoringState, state = ?TABLE_STATE_FINISHED, timer = TRef},
  314. NewTables = store_table(NewTable, Tables),
  315. send_to_table(TableModule, TablePid, show_round_result),
  316. {next_state, ?STATE_PROCESSING, StateData#state{tables = NewTables}};
  317. handle_table_message(TableId, {response, RequestId, Response},
  318. ?STATE_PROCESSING,
  319. #state{game_id = GameId, tab_requests = TabRequests} = StateData)
  320. when is_integer(TableId) ->
  321. NewTabRequests = dict:erase(RequestId, TabRequests),
  322. case dict:find(RequestId, TabRequests) of
  323. {ok, ReqContext} ->
  324. ?INFO("TRN_LUCKY <~p> A response received from table <~p>. "
  325. "RequestId: ~p. Request context: ~p. Response: ~p",
  326. [GameId, TableId, RequestId, ReqContext, Response]),
  327. handle_table_response(ReqContext, Response, ?STATE_PROCESSING,
  328. StateData#state{tab_requests = NewTabRequests});
  329. error ->
  330. ?ERROR("TRN_LUCKY <~p> Table <~p> sent a response for unknown request. "
  331. "RequestId: ~p. Response", []),
  332. {next_state, ?STATE_PROCESSING, StateData#state{tab_requests = NewTabRequests}}
  333. end;
  334. handle_table_message(_TableId, _Event, StateName, StateData) ->
  335. {next_state, StateName, StateData}.
  336. %%===================================================================
  337. %% handle_table_response(RequestContext, Response, StateName, StateData)
  338. handle_table_response({replace_player, PlayerId, TableId, SeatNum}, ok = _Response,
  339. ?STATE_PROCESSING,
  340. #state{reg_requests = RegRequests, seats = Seats,
  341. tables = Tables, table_module = TableModule} = StateData) ->
  342. Seat = fetch_seat(TableId, SeatNum, Seats),
  343. NewSeats = store_seat(Seat#seat{registered_by_table = true}, Seats),
  344. %% Send response to a client for a delayed request
  345. NewRegRequests =
  346. case dict:find(PlayerId, RegRequests) of
  347. {ok, From} ->
  348. #table{relay = Relay, pid = TablePid} = fetch_table(TableId, Tables),
  349. gen_fsm:reply(From, {ok, {PlayerId, Relay, {TableModule, TablePid}}}),
  350. dict:erase(PlayerId, RegRequests);
  351. error -> RegRequests
  352. end,
  353. {next_state, ?STATE_PROCESSING, StateData#state{seats = NewSeats,
  354. reg_requests = NewRegRequests}}.
  355. %%===================================================================
  356. handle_client_request({reg, User}, From, ?STATE_PROCESSING,
  357. #state{game_id = GameId, reg_requests = RegRequests,
  358. seats = Seats, players=Players, tables = Tables,
  359. table_module = TableModule} = StateData) ->
  360. #'PlayerInfo'{id = UserId, robot = IsBot} = User,
  361. ?INFO("TRN_LUCKY <~p> The Register request received from user: ~p.", [GameId, UserId]),
  362. case IsBot of
  363. true -> %% Bots can't initiate a registration
  364. case get_player_id_by_user_id(UserId, Players) of
  365. {ok, PlayerId} -> %% Already registered. Send table requsites.
  366. [#seat{table = TableId, registered_by_table = RegByTable}] = find_seats_by_player_id(PlayerId, Seats),
  367. case RegByTable of
  368. false -> %% Store delayed request
  369. NewRegRequests = dict:store(PlayerId, From, RegRequests),
  370. {next_state, ?STATE_PROCESSING, StateData#state{reg_requests = NewRegRequests}};
  371. _ ->
  372. #table{relay = Relay, pid = TPid} = fetch_table(TableId, Tables),
  373. {reply, {ok, {PlayerId, Relay, {TableModule, TPid}}}, ?STATE_PROCESSING, StateData}
  374. end;
  375. error -> %% Not registered
  376. ?INFO("TRN_LUCKY <~p> User ~p is a bot. The user not registered. "
  377. "Rejecting registration.", [GameId, UserId]),
  378. {reply, {error, indy_bots_not_allowed}, ?STATE_PROCESSING, StateData}
  379. end;
  380. false -> %% Normal user
  381. IgnoredPlayers = [Id || #player{id = Id} <- midict:geti(UserId, user_id, Players)],
  382. ?INFO("TRN_LUCKY <~p> There are no table with free seats.", [GameId]),
  383. case find_bot_seat_without_players(Seats, IgnoredPlayers) of
  384. #seat{table = TabId, seat_num = SeatNum, player_id = OldPlayerId} ->
  385. ?INFO("TRN_LUCKY <~p> Found a seat with a bot. Replacing by the user. "
  386. "UserId:~p TabId: ~p SeatNum: ~p.", [GameId, UserId, TabId, SeatNum]),
  387. reg_player_with_replace(User, TabId, SeatNum, OldPlayerId, From, StateData);
  388. not_found ->
  389. ?INFO("TRN_LUCKY <~p> There are no seats with bots. "
  390. "Creating new table for user: ~p.", [GameId, UserId]),
  391. reg_player_at_new_table(User, From, StateData)
  392. end
  393. end;
  394. handle_client_request(Request, From, StateName, #state{game_id = GameId} = StateData) ->
  395. ?INFO("TRN_LUCKY <~p> Unhandled client request received from ~p in "
  396. "state <~p>: ~p.", [GameId, From, StateName, Request]),
  397. {reply, {error, unexpected_request}, StateName, StateData}.
  398. %%===================================================================
  399. reg_player_with_replace(UserInfo, TableId, SeatNum, OldPlayerId, From,
  400. #state{game_id = GameId, players = Players, tables = Tables,
  401. seats = Seats, player_id_counter = PlayerId,
  402. tab_requests = TabRequests, reg_requests = RegRequests,
  403. table_module = TableModule} = StateData) ->
  404. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  405. NewPlayers = del_player(OldPlayerId, Players),
  406. NewPlayers2 = reg_player(#player{id = PlayerId, user_id = UserId, is_bot = IsBot}, NewPlayers),
  407. ?INFO("TRN_LUCKY <~p> User ~p registered as player <~p>.", [GameId, UserId, PlayerId]),
  408. NewSeats = assign_seat(TableId, SeatNum, PlayerId, IsBot, false, false, Seats),
  409. ?INFO("TRN_LUCKY <~p> User ~p assigned to seat <~p> of table <~p>.", [GameId, UserId, SeatNum, TableId]),
  410. NewRegRequests = dict:store(PlayerId, From, RegRequests),
  411. TablePid = get_table_pid(TableId, Tables),
  412. NewTabRequests = table_req_replace_player(TableModule, TablePid, PlayerId, UserInfo, TableId, SeatNum, TabRequests),
  413. {next_state, ?STATE_PROCESSING, StateData#state{players = NewPlayers2,
  414. seats = NewSeats,
  415. player_id_counter = PlayerId + 1,
  416. tab_requests = NewTabRequests,
  417. reg_requests = NewRegRequests}}.
  418. reg_player_at_new_table(User, From,
  419. #state{game_id = GameId, players = Players,
  420. tables = Tables, seats = Seats, seats_per_table = SeatsNum,
  421. player_id_counter = PlayerIdCounter,
  422. table_id_counter = TableId, table_module = TableModule,
  423. bot_module = BotModule, table_params = TableParams,
  424. reg_requests = RegRequests, cr_tab_requests = TCrRequests
  425. } = StateData) ->
  426. #'PlayerInfo'{id = UserId, robot = IsBot} = User,
  427. RobotsInfo = spawn_bots(GameId, BotModule, SeatsNum - 1),
  428. ?INFO("TRN_LUCKY <~p> Bots for table <~p> are spawned.", [GameId, TableId]),
  429. F = fun(BotInfo, {PlId,SNum}) -> {{PlId, BotInfo, SNum, _Points = 0}, {PlId + 1, SNum + 1}} end,
  430. {RobotsRegData, {PlayerId, SeatNum}} = lists:mapfoldl(F, {PlayerIdCounter, 1}, RobotsInfo),
  431. TPlayers = [{PlayerId, User, SeatNum, 0} | RobotsRegData],
  432. TableParams2 = [{players, TPlayers}, {table_name, "I'm filling lucky"} | TableParams],
  433. {ok, TabPid} = spawn_table(TableModule, GameId, TableId, TableParams2),
  434. MonRef = erlang:monitor(process, TabPid),
  435. %% FIXME: Table global id should use a persistent counter
  436. NewTables = reg_table(TableId, TabPid, MonRef, 0, undefined, Tables),
  437. ?INFO("TRN_LUCKY <~p> New table created: ~p.", [GameId, TableId]),
  438. NewPlayers = reg_player(#player{id = PlayerId, user_id = UserId, is_bot = IsBot}, Players),
  439. F2 = fun({PlId, #'PlayerInfo'{id = UId}, _SNum, _Points}, Acc) ->
  440. reg_player(#player{id = PlId, user_id = UId, is_bot = true}, Acc)
  441. end,
  442. NewPlayers2 = lists:foldl(F2, NewPlayers, RobotsRegData),
  443. ?INFO("TRN_LUCKY <~p> User ~p registered as player <~p>.", [GameId, UserId, PlayerId]),
  444. NewSeats = assign_seat(TableId, SeatNum, PlayerId, IsBot, false, false, Seats),
  445. F3 = fun({PlId, _UserInfo, SNum, _Points}, Acc) ->
  446. assign_seat(TableId, SNum, PlId, true, false, false, Acc)
  447. end,
  448. NewSeats2 = lists:foldl(F3, NewSeats, RobotsRegData),
  449. ?INFO("TRN_LUCKY <~p> User ~p assigned to seat <~p> of table <~p>.", [GameId, UserId, SeatNum, TableId]),
  450. NewRegRequests = dict:store(PlayerId, From, RegRequests),
  451. PlayersIds = [PlayerId | [PlId || {PlId, _, _, _} <- RobotsRegData]],
  452. NewTCrRequests = dict:store(TableId, PlayersIds, TCrRequests),
  453. {next_state, ?STATE_PROCESSING, StateData#state{players = NewPlayers2,
  454. seats = NewSeats2,
  455. tables = NewTables,
  456. player_id_counter = PlayerId + 1,
  457. table_id_counter = TableId + 1,
  458. reg_requests = NewRegRequests,
  459. cr_tab_requests = NewTCrRequests}}.
  460. unreg_player_and_eliminate_table(PlayerId, TableId,
  461. #state{players = Players, tables = Tables,
  462. table_module = TableModule, seats = Seats} = StateData) ->
  463. NewPlayers = del_player(PlayerId, Players),
  464. TablePid = get_table_pid(TableId, Tables),
  465. NewSeats = del_seats_by_table_id(TableId, Seats),
  466. NewTables = del_table(TableId, Tables),
  467. send_to_table(TableModule, TablePid, stop),
  468. {next_state, ?STATE_PROCESSING, StateData#state{players = NewPlayers,
  469. seats = NewSeats,
  470. tables = NewTables}}.
  471. replace_player_by_bot(PlayerId, TableId, SeatNum,
  472. #state{players = Players, seats = Seats, game_id = GameId,
  473. bot_module = BotModule, table_module = TableModule,
  474. player_id_counter = NewPlayerId, tables = Tables,
  475. tab_requests = Requests} = StateData) ->
  476. NewPlayers = del_player(PlayerId, Players),
  477. [#'PlayerInfo'{id = UserId} = UserInfo] = spawn_bots(GameId, BotModule, 1),
  478. NewPlayers2 = reg_player(#player{id = NewPlayerId, user_id = UserId, is_bot = true}, NewPlayers),
  479. NewSeats = assign_seat(TableId, SeatNum, NewPlayerId, true, false, false, Seats),
  480. TablePid = get_table_pid(TableId, Tables),
  481. NewRequests = table_req_replace_player(TableModule, TablePid, NewPlayerId, UserInfo, TableId, SeatNum, Requests),
  482. {next_state, ?STATE_PROCESSING, StateData#state{players = NewPlayers2,
  483. seats = NewSeats,
  484. player_id_counter = NewPlayerId + 1,
  485. tab_requests = NewRequests}}.
  486. %% table_req_replace_player(TableModule, TablePid, PlayerId, UserInfo, TableId, SeatNum, TabRequests) -> NewRequests
  487. table_req_replace_player(TableModule, TablePid, PlayerId, UserInfo, TableId, SeatNum, TabRequests) ->
  488. RequestId = make_ref(),
  489. NewRequests = dict:store(RequestId, {replace_player, PlayerId, TableId, SeatNum}, TabRequests),
  490. send_to_table(TableModule, TablePid, {replace_player, RequestId, UserInfo, PlayerId, SeatNum}),
  491. NewRequests.
  492. %% players_init() -> players()
  493. players_init() ->
  494. midict:new().
  495. %% reg_player(#player{}, Players) -> NewPlayers
  496. reg_player(#player{id =Id, user_id = UserId} = Player, Players) ->
  497. midict:store(Id, Player, [{user_id, UserId}], Players).
  498. get_player_id_by_user_id(UserId, Players) ->
  499. case midict:geti(UserId, user_id, Players) of
  500. [#player{id = PlayerId}] -> {ok, PlayerId};
  501. [] -> error
  502. end.
  503. %% del_player(PlayerId, Players) -> NewPlayers
  504. del_player(PlayerId, Players) ->
  505. midict:erase(PlayerId, Players).
  506. %% del_player(PlayersIds, Players) -> NewPlayers
  507. del_players([], Players) -> Players;
  508. del_players([PlayerId | Rest], Players) ->
  509. del_players(Rest, del_player(PlayerId, Players)).
  510. tables_init() ->
  511. midict:new().
  512. reg_table(TableId, Pid, MonRef, GlobalId, Scoring, Tables) ->
  513. Table = #table{id = TableId, pid = Pid, mon_ref = MonRef, global_id = GlobalId,
  514. state = initializing, scoring_state = Scoring},
  515. store_table(Table, Tables).
  516. update_created_table(TableId, Relay, Tables) ->
  517. Table = midict:fetch(TableId, Tables),
  518. NewTable = Table#table{relay = Relay, state = ?TABLE_STATE_READY},
  519. store_table(NewTable, Tables).
  520. store_table(#table{id = TableId, pid = Pid, mon_ref = MonRef, global_id = GlobalId} = Table, Tables) ->
  521. midict:store(TableId, Table, [{pid, Pid}, {global_id, GlobalId}, {mon_ref, MonRef}], Tables).
  522. fetch_table(TableId, Tables) ->
  523. midict:fetch(TableId, Tables).
  524. get_table(TableId, Tables) ->
  525. midict:find(TableId, Tables).
  526. get_table_pid(TabId, Tables) ->
  527. {ok, #table{pid = TabPid}} = midict:find(TabId, Tables),
  528. TabPid.
  529. del_table(TabId, Tables) ->
  530. midict:erase(TabId, Tables).
  531. get_table_by_mon_ref(MonRef, Tables) ->
  532. case midict:geti(MonRef, mon_ref, Tables) of
  533. [Table] -> Table;
  534. [] -> not_found
  535. end.
  536. set_table_state(TableId, State, Tables) ->
  537. Table = midict:fetch(TableId, Tables),
  538. store_table(Table#table{state = State}, Tables).
  539. seats_init() ->
  540. midict:new().
  541. find_bot_seat_without_players(Seats, PlayersList) ->
  542. case midict:geti(true, is_bot, Seats) of
  543. [] -> not_found;
  544. List ->
  545. TabList = lists:usort([TabId || #seat{table = TabId} <- List]),
  546. lookup_bot_seat_without_players(TabList, PlayersList, Seats)
  547. end.
  548. lookup_bot_seat_without_players([], _, _) -> not_found;
  549. lookup_bot_seat_without_players([TabId | Rest], PlayersList, Seats) ->
  550. TabPlayers = [Id || #seat{player_id=Id} <-
  551. midict:geti(TabId, non_free_at_tab, Seats), lists:member(Id, PlayersList)],
  552. if TabPlayers == [] ->
  553. ?INFO("TRN_LUCKY Seats:~p", [midict:geti(TabId, table_id, Seats)]),
  554. hd(midict:geti(TabId, bot_at_tab, Seats));
  555. true -> lookup_bot_seat_without_players(Rest, PlayersList, Seats)
  556. end.
  557. find_seats_with_players_for_table_id(TabId, Seats) ->
  558. midict:geti(TabId, non_free_at_tab, Seats).
  559. find_seats_by_player_id(PlayerId, Seats) ->
  560. midict:geti(PlayerId, player_id, Seats).
  561. find_seats_by_table_id(TabId, Seats) ->
  562. midict:geti(TabId, table_id, Seats).
  563. %% real_players_at_table(TabId, Seats) -> Num
  564. real_players_at_table(TabId, Seats) ->
  565. length(find_real_players_seats_at_tab(TabId, Seats)).
  566. is_all_players_connected(TableId, TableSeatsNum, Seats) ->
  567. TableSeatsNum == length(midict:geti(true, {connected, TableId}, Seats)).
  568. find_real_players_seats_at_tab(TabId, Seats) ->
  569. midict:geti(TabId, real_player_at_tab, Seats).
  570. fetch_seat(TableId, SeatNum, Seats) ->
  571. midict:fetch({TableId, SeatNum}, Seats).
  572. %% assign_seat(TabId, SeatNum, PlayerId, IsBot, RegByTable, Connected, Seats) -> NewSeats
  573. %% PlayerId = integer()
  574. %% IsBot = RegByTable = Connected = undefined | boolean()
  575. assign_seat(TabId, SeatNum, PlayerId, IsBot, RegByTable, Connected, Seats) ->
  576. Seat = #seat{table = TabId, seat_num = SeatNum, player_id = PlayerId,
  577. is_bot = IsBot, registered_by_table = RegByTable, connected = Connected},
  578. store_seat(Seat, Seats).
  579. update_seat_connect_status(TableId, SeatNum, ConnStatus, Seats) ->
  580. Seat = midict:fetch({TableId, SeatNum}, Seats),
  581. NewSeat = Seat#seat{connected = ConnStatus},
  582. store_seat(NewSeat, Seats).
  583. store_seat(#seat{table = TabId, seat_num = SeatNum, player_id = PlayerId,
  584. is_bot = IsBot, registered_by_table = _RegByTable,
  585. connected = Connected} = Seat, Seats) ->
  586. Indices = if PlayerId == undefined ->
  587. [{table_id, TabId}, {free, true}, {free_at_tab, TabId}];
  588. true ->
  589. I = [{table_id, TabId}, {free, false}, {non_free_at_tab, TabId},
  590. {player_id, PlayerId}, {is_bot, IsBot},
  591. {{connected, TabId}, Connected}],
  592. if IsBot -> [{bot_at_tab, TabId} | I];
  593. true -> [{real_player_at_tab, TabId} | I]
  594. end
  595. end,
  596. midict:store({TabId, SeatNum}, Seat, Indices, Seats).
  597. create_seats(_TabId, 0, Seats) -> Seats;
  598. create_seats(TabId, SeatNum, Seats) ->
  599. NewSeats = assign_seat(TabId, SeatNum, undefined, undefined, undefined, undefined, Seats),
  600. create_seats(TabId, SeatNum - 1, NewSeats).
  601. del_seats_by_table_id(TabId, Seats) ->
  602. F = fun(#seat{seat_num = SeatNum}, Acc) ->
  603. midict:erase({TabId, SeatNum}, Acc)
  604. end,
  605. lists:foldl(F, Seats, find_seats_by_table_id(TabId, Seats)).
  606. spawn_bots(GameId, BotModule, BotsNum) ->
  607. [spawn_bot(BotModule, GameId) || _ <- lists:seq(1, BotsNum)].
  608. spawn_bot(BotModule, GameId) ->
  609. {NPid, UserInfo} = create_robot(BotModule, GameId),
  610. BotModule:join_game(NPid),
  611. UserInfo.
  612. create_robot(BotModule, GameId) ->
  613. UserInfo = auth_server:robot_credentials(),
  614. {ok, NPid} = BotModule:start(self(), UserInfo, GameId),
  615. BotModule:get_session(NPid), %% Hack for the game_tavla_bot. Creates a game session process.
  616. {NPid, UserInfo}.
  617. spawn_table(TabMod, GameId, TableId, Params) ->
  618. Pid = TabMod:start(GameId, TableId, Params),
  619. Pid.
  620. send_to_table(TabMod, TabPid, Message) ->
  621. TabMod:parent_message(TabPid, Message).
  622. get_param(ParamId, Params) ->
  623. {_, Value} = lists:keyfind(ParamId, 1, Params),
  624. Value.