okey_table.erl 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510
  1. %%% -------------------------------------------------------------------
  2. %%% Author : Sergii Polkovnikov <serge.polkovnikov@gmail.com>
  3. %%% Description :
  4. %%%
  5. %%% Created : Oct 15, 2012
  6. %%% -------------------------------------------------------------------
  7. -module(okey_table).
  8. -behaviour(gen_fsm).
  9. %% --------------------------------------------------------------------
  10. %% Include files
  11. %% --------------------------------------------------------------------
  12. -include_lib("server/include/basic_types.hrl").
  13. -include_lib("server/include/settings.hrl").
  14. -include_lib("server/include/game_okey.hrl").
  15. -include_lib("server/include/game_state.hrl").
  16. -include_lib("server/include/requests.hrl").
  17. -include_lib("db/include/journal.hrl").
  18. -include_lib("kvs/include/user.hrl").
  19. %% --------------------------------------------------------------------
  20. %% External exports
  21. -export([start/3,
  22. player_action/3,
  23. parent_message/2,
  24. relay_message/2
  25. ]).
  26. -export([submit/3, signal/3]).
  27. %% gen_fsm callbacks
  28. -export([init/1, handle_event/3, handle_sync_event/4,
  29. handle_info/3, terminate/3, code_change/4]).
  30. -type tash() :: false_okey | {integer(), integer()}.
  31. -record(desk_state,
  32. {
  33. state :: state_take | state_discard | state_finished,
  34. hands :: list({integer(), list(tash())}), %% {SeatNum, Tashes}
  35. discarded :: list({integer(), list(tash())}), %% {SeatNum, Tashes}
  36. deck :: list(tash()),
  37. cur_seat :: integer(),
  38. gosterge :: tash(),
  39. has_gosterge :: undefined | integer(), %% Seat num of a player who has gosterge
  40. have_8_tashes :: list(integer()), %% Seats of players who show 8 tashes combination
  41. finish_reason :: tashes_out | reveal | gosterge_finish, %% Defined only when state = state_finished
  42. finish_info :: term()
  43. }).
  44. -record(player, {?PLAYER}).
  45. -define(STATE_WAITING_FOR_START, state_waiting_for_start).
  46. -define(STATE_PLAYING, state_playing).
  47. -define(STATE_REVEAL_CONFIRMATION, state_reveal_confirmation).
  48. -define(STATE_FINISHED, state_finished).
  49. -define(STATE_PAUSE, state_pause).
  50. -define(STATE_SET_FINISHED, state_set_finished).
  51. -define(HAND_SIZE, 14).
  52. -define(SEATS_NUM, 4).
  53. -define(RELAY, relay).
  54. -define(DESK, okey_desk).
  55. -define(GAME_STATS, journal).
  56. -define(SCORING, okey_scoring).
  57. %% ====================================================================
  58. %% External functions
  59. %% ====================================================================
  60. start(GameId, TableId, TableParams) ->
  61. gen_fsm:start(?MODULE, [GameId, TableId, TableParams], []).
  62. player_action(Srv, PlayerId, Action) ->
  63. gen_fsm:sync_send_all_state_event(Srv, {player_action, PlayerId, Action}).
  64. parent_message(Srv, Message) ->
  65. gen_fsm:send_all_state_event(Srv, {parent_message, Message}).
  66. relay_message(Srv, Message) ->
  67. gen_fsm:send_all_state_event(Srv, {relay_message, Message}).
  68. submit(Table, PlayerId, Action) ->
  69. player_action(Table, PlayerId, {submit, Action}).
  70. signal(Table, PlayerId, Signal) ->
  71. player_action(Table, PlayerId, {signal, Signal}).
  72. %% ====================================================================
  73. %% Server functions
  74. %% ====================================================================
  75. init([GameId, TableId, Params]) ->
  76. Parent = proplists:get_value(parent, Params),
  77. PlayersInfo = proplists:get_value(players, Params),
  78. TableName = proplists:get_value(table_name, Params),
  79. MultFactor = proplists:get_value(mult_factor, Params),
  80. SlangFlag = proplists:get_value(slang_allowed, Params),
  81. ObserversFlag = proplists:get_value(observers_allowed, Params),
  82. TournamentType = proplists:get_value(tournament_type, Params),
  83. Speed = proplists:get_value(speed, Params),
  84. TurnTimeout = proplists:get_value(turn_timeout, Params, get_timeout(turn, Speed)), %% TODO Set this param explictly
  85. RevConfirmTimeout = proplists:get_value(reveal_confirmation_timeout, Params, get_timeout(challenge, Speed)), %% TODO Set this param explictly
  86. ReadyTimeout = proplists:get_value(ready_timeout, Params, get_timeout(ready, Speed)), %% TODO Set this param explictly
  87. RoundTimeout = proplists:get_value(round_timeout, Params),
  88. SetTimeout = proplists:get_value(set_timeout, Params),
  89. GameMode = proplists:get_value(game_type, Params),
  90. Rounds = proplists:get_value(rounds, Params),
  91. RevealConfirmation = proplists:get_value(reveal_confirmation, Params),
  92. NextSeriesConfirmation = proplists:get_value(next_series_confirmation, Params),
  93. PauseMode = proplists:get_value(pause_mode, Params),
  94. GostergeFinishAllowed = proplists:get_value(gosterge_finish_allowed, Params),
  95. SocialActionsEnabled = proplists:get_value(social_actions_enabled, Params),
  96. TTable = proplists:get_value(ttable, Params),
  97. Tour = proplists:get_value(tour, Params),
  98. Tours = proplists:get_value(tours, Params),
  99. %% Next two options will be passed on table respawn (after fail or service maintaince)
  100. ScoringState = proplists:get_value(scoring_state, Params, init_scoring(GameMode, PlayersInfo, Rounds)),
  101. CurRound = proplists:get_value(cur_round, Params, 0),
  102. Players = init_players(PlayersInfo),
  103. RelayParams = [{players, [{PlayerId, UserInfo#'PlayerInfo'.id} || {PlayerId, UserInfo, _, _} <- PlayersInfo]},
  104. {observers_allowed, false},
  105. {table, {?MODULE, self()}}],
  106. {ok, Relay} = ?RELAY:start(RelayParams),
  107. gas:info(?MODULE,"OKEY_NG_TABLE_TRN_DBG <~p,~p> Set timeout: ~p, round timeout: ~p.", [GameId, TableId, SetTimeout, RoundTimeout]),
  108. gas:info(?MODULE,"OKEY_NG_TABLE_TRN_DBG <~p,~p> PlayersInfo: ~p.", [GameId, TableId, PlayersInfo]),
  109. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Started.", [GameId, TableId]),
  110. parent_notify_table_created(Parent, TableId, Relay),
  111. {ok, ?STATE_WAITING_FOR_START, #okey_state{game_id = GameId,
  112. table_id = TableId,
  113. table_name = TableName,
  114. parent = Parent,
  115. relay = Relay,
  116. mult_factor = MultFactor,
  117. slang_flag = SlangFlag,
  118. observer_flag = ObserversFlag,
  119. tournament_type = TournamentType,
  120. tour = Tour,
  121. tours = Tours,
  122. speed = Speed,
  123. turn_timeout = TurnTimeout,
  124. reveal_confirmation_timeout = RevConfirmTimeout,
  125. ready_timeout = ReadyTimeout,
  126. round_timeout = RoundTimeout,
  127. set_timeout = SetTimeout,
  128. game_mode = GameMode,
  129. rounds = Rounds,
  130. reveal_confirmation = RevealConfirmation,
  131. next_series_confirmation = NextSeriesConfirmation,
  132. pause_mode = PauseMode,
  133. gosterge_finish_allowed = GostergeFinishAllowed,
  134. social_actions_enabled = SocialActionsEnabled,
  135. players = Players,
  136. start_seat = crypto:rand_uniform(1, ?SEATS_NUM + 1),
  137. cur_round = CurRound,
  138. scoring_state = ScoringState,
  139. tournament_table = TTable
  140. }}.
  141. handle_event({parent_message, Message}, StateName,
  142. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  143. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received message from the parent: ~p.",
  144. [GameId, TableId, Message]),
  145. handle_parent_message(Message, StateName, StateData);
  146. handle_event({relay_message, Message}, StateName,
  147. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  148. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received message from the relay: ~p.",
  149. [GameId, TableId, Message]),
  150. handle_relay_message(Message, StateName, StateData);
  151. handle_event(_Event, StateName, StateData) ->
  152. {next_state, StateName, StateData}.
  153. handle_sync_event({player_action, PlayerId, Action}, From, StateName,
  154. #okey_state{players = Players} = StateData) ->
  155. case get_player(PlayerId, Players) of
  156. {ok, Player} ->
  157. handle_player_action(Player, Action, From, StateName, StateData);
  158. error ->
  159. {reply, {error, you_are_not_a_player}, StateName, StateData}
  160. end;
  161. handle_sync_event(_Event, _From, StateName, StateData) ->
  162. Reply = ok,
  163. {reply, Reply, StateName, StateData}.
  164. handle_info({timeout, Magic}, ?STATE_PLAYING,
  165. #okey_state{timeout_magic = Magic, game_id = GameId, table_id = TableId} = StateData) ->
  166. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Move timeout. Do an automatic move(s).", [GameId, TableId]),
  167. do_timeout_moves(StateData);
  168. handle_info({timeout, Magic}, State,
  169. #okey_state{timeout_magic = Magic, game_id = GameId, table_id = TableId} = StateData) ->
  170. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Move timeout. Do an automatic move(s). UNKNOWN STATE ~p", [GameId, TableId,State]),
  171. do_timeout_moves(StateData);
  172. handle_info({round_timeout, Round}, ?STATE_PLAYING,
  173. #okey_state{cur_round = Round, desk_state = DeskState, game_id = GameId,
  174. table_id = TableId, timeout_timer = TRef} = StateData) ->
  175. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Time to finish round ~p because the round timeout.", [GameId, TableId, Round]),
  176. if TRef =/= undefined -> erlang:cancel_timer(TRef);
  177. true -> do_nothing
  178. end,
  179. finalize_round(StateData#okey_state{desk_state = DeskState#desk_state{finish_reason = timeout}});
  180. handle_info(set_timeout, StateName,
  181. #okey_state{cur_round = Round, desk_state = DeskState, game_id = GameId,
  182. table_id = TableId, timeout_timer = TRef} = StateData) when
  183. StateName =/= ?STATE_SET_FINISHED ->
  184. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Time to finish round ~p and the set because the set timeout.", [GameId, TableId, Round]),
  185. if TRef =/= undefined -> erlang:cancel_timer(TRef);
  186. true -> do_nothing
  187. end,
  188. finalize_round(StateData#okey_state{desk_state = DeskState#desk_state{finish_reason = set_timeout}});
  189. handle_info({timeout, Magic}, ?STATE_REVEAL_CONFIRMATION,
  190. #okey_state{timeout_magic = Magic, wait_list = WL, game_id = GameId, table_id = TableId,
  191. reveal_confirmation_list = CList} = StateData) ->
  192. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Time to check reveal confirmation responses.", [GameId, TableId]),
  193. NewCList = lists:foldl(fun(SeatNum, Acc) -> [{SeatNum, false} | Acc] end, CList, WL),
  194. finalize_round(StateData#okey_state{reveal_confirmation_list = NewCList});
  195. handle_info(Info, StateName, #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  196. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Unexpected message(info) received at state <~p>: ~p.",
  197. [GameId, TableId, StateName, Info]),
  198. {next_state, StateName, StateData}.
  199. terminate(Reason, StateName, #okey_state{game_id = GameId, table_id = TableId, relay = Relay}) ->
  200. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Shutting down at state: <~p>. Reason: ~p",
  201. [GameId, TableId, StateName, Reason]),
  202. relay_stop(Relay),
  203. ok.
  204. code_change(_OldVsn, StateName, StateData, _Extra) ->
  205. {ok, StateName, StateData}.
  206. %% --------------------------------------------------------------------
  207. %%% Internal functions
  208. %% --------------------------------------------------------------------
  209. %% handle_parent_message(Msg, StateName, StateData)
  210. handle_parent_message({register_player, RequestId, UserInfo, PlayerId, SeatNum}, StateName,
  211. #okey_state{table_id = TableId, players = Players,
  212. parent = Parent, relay = Relay} = StateData) ->
  213. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  214. NewPlayers = reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, _Connected = false, Players),
  215. relay_register_player(Relay, UserId, PlayerId),
  216. %% TODO: Send notificitations to gamesessions (we have no such notification)
  217. parent_confirm_registration(Parent, TableId, RequestId),
  218. {next_state, StateName, StateData#okey_state{players = NewPlayers}};
  219. handle_parent_message({replace_player, RequestId, UserInfo, PlayerId, SeatNum}, StateName,
  220. #okey_state{table_id = TableId, players = Players,
  221. parent = Parent, relay = Relay} = StateData) ->
  222. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  223. #player{id = OldPlayerId} = get_player_by_seat_num(SeatNum, Players),
  224. NewPlayers = del_player(OldPlayerId, Players),
  225. NewPlayers2 = reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, _Connected = false, NewPlayers),
  226. relay_kick_player(Relay, OldPlayerId),
  227. relay_register_player(Relay, UserId, PlayerId),
  228. ReplaceMsg = create_player_left(SeatNum, UserInfo, Players),
  229. relay_publish_ge(Relay, ReplaceMsg, StateData),
  230. parent_confirm_replacement(Parent, TableId, RequestId),
  231. {next_state, StateName, StateData#okey_state{players = NewPlayers2}};
  232. handle_parent_message(start_round, StateName,
  233. #okey_state{game_mode = GameMode, cur_round = CurRound,
  234. gosterge_finish_allowed = GostergeFinishAllowed,
  235. start_seat = LastStartSeat, players = Players,
  236. relay = Relay, turn_timeout = TurnTimeout,
  237. round_timeout = RoundTimeout, set_timeout = SetTimeout,
  238. set_timer = SetTRef, scoring_state = ScoringState} = StateData)
  239. when StateName == ?STATE_WAITING_FOR_START;
  240. StateName == ?STATE_FINISHED ->
  241. NewCurRound = CurRound + 1,
  242. StartSeat = next_seat_num(LastStartSeat),
  243. Deck = deck:shuffle(deck:init_deck(okey)),
  244. {Gosterge, Deck1} = choose_gosterge(Deck),
  245. F = fun(SeatNum, AccDeck) ->
  246. Num = if SeatNum==StartSeat -> ?HAND_SIZE + 1; true -> ?HAND_SIZE end,
  247. lists:split(Num, AccDeck)
  248. end,
  249. {Hands, TablePile} = lists:mapfoldl(F, deck:to_list(Deck1), lists:seq(1, ?SEATS_NUM)),
  250. GostFinishList = if GameMode == countdown andalso GostergeFinishAllowed andalso NewCurRound > 1 ->
  251. {_,_,_,Scores} = ?SCORING:last_round_result(ScoringState),
  252. [SeatNum || {SeatNum, 1} <- Scores];
  253. true -> []
  254. end,
  255. Have8TashesEnabled = GameMode == evenodd orelse GameMode == color,
  256. Params = [{hands, Hands},
  257. {deck, TablePile},
  258. {gosterge, Gosterge},
  259. {cur_player, StartSeat},
  260. {gosterge_finish_list, GostFinishList},
  261. {have_8_tashes_enabled, Have8TashesEnabled}],
  262. {ok, Desk} = ?DESK:start(Params),
  263. DeskState = init_desk_state(Desk),
  264. %% Init timers
  265. {Magic, TRef} = start_timer(TurnTimeout),
  266. RoundTRef = if is_integer(RoundTimeout) ->
  267. erlang:send_after(RoundTimeout, self(), {round_timeout, NewCurRound});
  268. true -> undefined
  269. end,
  270. NewSetTRef = if NewCurRound == 1 ->
  271. if is_integer(SetTimeout) -> erlang:send_after(SetTimeout, self(), set_timeout);
  272. true -> undefined
  273. end;
  274. true -> SetTRef
  275. end,
  276. NewStateData = StateData#okey_state{cur_round = NewCurRound,
  277. start_seat = StartSeat,
  278. desk_rule_pid = Desk,
  279. desk_state = DeskState,
  280. timeout_timer = TRef,
  281. timeout_magic = Magic,
  282. round_timer = RoundTRef,
  283. set_timer = NewSetTRef},
  284. [begin
  285. GameInfoMsg = create_okey_game_info(NewStateData),
  286. send_to_client_ge(Relay, PlayerId, GameInfoMsg, NewStateData),
  287. GameStartedMsg = create_okey_game_started(SeatNum, DeskState, NewCurRound, NewStateData),
  288. send_to_client_ge(Relay, PlayerId, GameStartedMsg, NewStateData)
  289. end || #player{id = PlayerId, seat_num = SeatNum} <- find_connected_players(Players)],
  290. CurSeatNum = DeskState#desk_state.cur_seat,
  291. relay_publish_ge(Relay, create_okey_next_turn(CurSeatNum, Players), NewStateData),
  292. {next_state, ?STATE_PLAYING, NewStateData};
  293. handle_parent_message(show_round_result, StateName,
  294. #okey_state{relay = Relay, scoring_state = ScoringState,
  295. game_id = GameId, table_id = TableId} = StateData) ->
  296. {FinishInfo, RoundScore, AchsPoints, TotalScore} = ?SCORING:last_round_result(ScoringState),
  297. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> RoundScore: ~p Total score: ~p.", [GameId, TableId, RoundScore, TotalScore]),
  298. {Reason,Revealer,RevealerWon,WrongRejects} = case FinishInfo of
  299. {win_reveal, R, Wrong, _Color, _Okey, _Pairs} -> {win_reveal,R,true,Wrong};
  300. {fail_reveal, R} -> {fail_reveal,R,false,[]};
  301. % {fail_reveal, R} -> {tashes_out,none,false,[]}; % check tashes out event
  302. tashes_out -> {tashes_out,none,false,[]};
  303. timeout -> {timeout,none,false,[]};
  304. set_timeout -> {timeout,none,false,[]};
  305. {gosterge_finish, Winner} -> {gosterge_finish,Winner,true,[]} end,
  306. Msg = round_results(Reason,Revealer,RevealerWon,WrongRejects,RoundScore,TotalScore,AchsPoints,StateData),
  307. relay_publish_ge(Relay, Msg, StateData),
  308. {next_state, StateName, StateData#okey_state{}};
  309. %% Results = [{PlayerId, Position, Score, Status}] Status = winner | loser | eliminated | none
  310. handle_parent_message({show_series_result, Results}, StateName,
  311. #okey_state{relay = Relay, players = Players,
  312. next_series_confirmation = Confirm} = StateData) ->
  313. Msg = create_okey_series_ended(Results, Players, Confirm, StateData),
  314. relay_publish_ge(Relay, Msg, StateData),
  315. {next_state, StateName, StateData#okey_state{}};
  316. %% Results = [{UserId, Position, Score, Status}] Status = active | eliminated
  317. handle_parent_message({tour_result, TourNum, Results}, StateName,
  318. #okey_state{relay = Relay, tournament_table = TTable} = StateData) ->
  319. NewTTable = [{TourNum, Results} | TTable],
  320. Msg = create_okey_tour_result(TourNum, Results),
  321. relay_publish_ge(Relay, Msg, StateData),
  322. {next_state, StateName, StateData#okey_state{tournament_table = NewTTable}};
  323. handle_parent_message({playing_tables_num, Num}, StateName,
  324. #okey_state{relay = Relay} = StateData) ->
  325. %%XXX Msg = create_okey_playing_tables(Num),
  326. %% relay_publish_ge(Relay, Msg),
  327. {next_state, StateName, StateData};
  328. handle_parent_message(rejoin_players, StateName,
  329. #okey_state{game_id = GameId, relay = Relay,
  330. players = Players} = StateData) ->
  331. [relay_unregister_player(Relay, P#player.id, {rejoin, GameId}) || P <- players_to_list(Players)],
  332. {next_state, StateName, StateData#okey_state{players = players_init()}};
  333. handle_parent_message(disconnect_players, StateName,
  334. #okey_state{relay = Relay, players = Players} = StateData) ->
  335. [relay_unregister_player(Relay, P#player.id, game_over) || P <- players_to_list(Players)],
  336. {next_state, StateName, StateData#okey_state{players = players_init()}};
  337. handle_parent_message(stop, _StateName, StateData) ->
  338. {stop, normal, StateData};
  339. handle_parent_message(Message, StateName,
  340. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  341. gas:error(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Unexpected parent message received in state <~p>: ~p. State: ~p. Stopping.",
  342. [GameId, TableId, StateName, Message, StateName]),
  343. {stop, unexpected_parent_message, StateData}.
  344. %%===================================================================
  345. %% handle_relay_message(Msg, StateName, StateData)
  346. handle_relay_message({player_connected, PlayerId} = Msg, StateName,
  347. #okey_state{parent = Parent, game_id = GameId,
  348. table_id = TableId, players = Players} = StateData) ->
  349. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received nofitication from the relay: ~p", [GameId, TableId, Msg]),
  350. case get_player(PlayerId, Players) of
  351. {ok, Player} ->
  352. NewPlayers = store_player_rec(Player#player{connected = true}, Players),
  353. parent_send_player_connected(Parent, TableId, PlayerId),
  354. {next_state, StateName, StateData#okey_state{players = NewPlayers}};
  355. error ->
  356. {next_state, StateName, StateData}
  357. end;
  358. handle_relay_message({player_disconnected, PlayerId}, StateName,
  359. #okey_state{parent = Parent, table_id = TableId, players = Players} = StateData) ->
  360. case get_player(PlayerId, Players) of
  361. {ok, Player} ->
  362. NewPlayers = store_player_rec(Player#player{connected = false}, Players),
  363. parent_send_player_disconnected(Parent, TableId, PlayerId),
  364. {next_state, StateName, StateData#okey_state{players = NewPlayers}};
  365. error ->
  366. {next_state, StateName, StateData}
  367. end;
  368. handle_relay_message({subscriber_added, PlayerId, SubscrId} = Msg, StateName,
  369. #okey_state{relay = Relay, game_id = GameId,
  370. table_id = TableId, tournament_table = TTable,
  371. players = Players} = StateData) ->
  372. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received nofitication from the relay: ~p", [GameId, TableId, Msg]),
  373. PlayerIdIsValid = case PlayerId of
  374. observer -> true;
  375. administrator -> true;
  376. _ ->
  377. case get_player(PlayerId, Players) of
  378. {ok, _} -> true;
  379. error -> false
  380. end
  381. end,
  382. if PlayerIdIsValid ->
  383. GI = create_okey_game_info(StateData),
  384. send_to_subscriber_ge(Relay, SubscrId, GI, StateData),
  385. PlState = create_okey_game_player_state(PlayerId, StateName, StateData),
  386. send_to_subscriber_ge(Relay, SubscrId, PlState, StateData),
  387. relay_allow_broadcast_for_player(Relay, PlayerId),
  388. if TTable =/= undefined ->
  389. [send_to_subscriber_ge(Relay, SubscrId, create_okey_tour_result(TurnNum, Results), StateData)
  390. || {TurnNum, Results} <- lists:sort(TTable)];
  391. true -> do_nothing
  392. end;
  393. true -> do_nothing
  394. end,
  395. {next_state, StateName, StateData};
  396. handle_relay_message(Message, StateName, #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  397. gas:error(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Unknown relay message received in state <~p>: ~p. State: ~p. Stopping.",
  398. [GameId, TableId, StateName, Message]),
  399. {next_state, StateName, StateData}.
  400. %%===================================================================
  401. %% handle_player_action(Player, Msg, StateName, StateData)
  402. handle_player_action(#player{id = PlayerId, seat_num = SeatNum, user_id = UserId},
  403. {submit, #game_action{action = Action, args = Args} = GA}, From,
  404. StateName,
  405. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  406. {Keys,Values} = lists:unzip(Args),
  407. ExtAction = list_to_tuple([Action|Values]),
  408. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Player <~p> (~p)~n submit the game action: ~p.",
  409. [GameId, TableId, PlayerId, UserId, ExtAction]),
  410. do_action(SeatNum, ExtAction, From, StateName, StateData);
  411. handle_player_action(#player{id = PlayerId, user_id = UserId},
  412. {signal, {pause_game, _}=Signal}, _From,
  413. StateName,
  414. #okey_state{table_id = TableId, game_id = GameId, timeout_timer = TRef,
  415. pause_mode = PauseMode, relay = Relay} = StateData) ->
  416. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received signal from player <~p> : ~p. PauseMode: ~p",
  417. [GameId, TableId, PlayerId, Signal, PauseMode]),
  418. case PauseMode of
  419. disabled ->
  420. {reply, {error, pause_disabled}, StateName, StateData};
  421. normal ->
  422. if StateName == ?STATE_PLAYING;
  423. StateName == ?STATE_REVEAL_CONFIRMATION ->
  424. Timeout = case erlang:cancel_timer(TRef) of
  425. false -> 0;
  426. T -> T
  427. end,
  428. relay_publish(Relay, create_game_paused_pause(UserId, GameId)),
  429. {reply, 0, ?STATE_PAUSE, StateData#okey_state{paused_statename = StateName,
  430. paused_timeout_value = Timeout,
  431. timeout_magic = undefined}};
  432. true ->
  433. {reply, {error, pause_not_possible}, StateName, StateData}
  434. end
  435. end;
  436. handle_player_action(#player{id = PlayerId, user_id = UserId},
  437. {signal, {resume_game, _}=Signal}, _From,
  438. StateName,
  439. #okey_state{table_id = TableId, game_id = GameId, pause_mode = PauseMode,
  440. relay = Relay, paused_statename = ResumedStateName,
  441. paused_timeout_value = Timeout} = StateData) ->
  442. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received signal from player <~p> : ~p. PauseMode: ~p",
  443. [GameId, TableId, PlayerId, Signal, PauseMode]),
  444. case PauseMode of
  445. disabled ->
  446. {reply, {error, pause_disabled}, StateName, StateData};
  447. normal ->
  448. if StateName == ?STATE_PAUSE ->
  449. relay_publish(Relay, create_game_paused_resume(UserId, GameId)),
  450. {Magic, TRef} = start_timer(Timeout),
  451. {reply, 0, ResumedStateName, StateData#okey_state{timeout_timer = TRef,
  452. timeout_magic = Magic}};
  453. true ->
  454. {reply, {error, game_is_not_paused}, StateName, StateData}
  455. end
  456. end;
  457. handle_player_action(#player{id = PlayerId},
  458. {signal, Signal}, _From, StateName,
  459. #okey_state{table_id = TableId, game_id = GameId} = StateData) ->
  460. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received signal from player <~p> : ~p. Ignoring.",
  461. [GameId, TableId, PlayerId, Signal]),
  462. {reply, ok, StateName, StateData};
  463. handle_player_action(_Player, _Message, _From, StateName, StateData) ->
  464. {next_state, StateName, StateData}.
  465. %%===================================================================
  466. do_action(SeatNum, #okey_has_gosterge{}, From, ?STATE_PLAYING = StateName, StateData) ->
  467. do_game_action(SeatNum, i_have_gosterge, From, StateName, StateData);
  468. do_action(_SeatNum, #okey_has_gosterge{}, _From, StateName, StateData) ->
  469. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  470. do_action(SeatNum, #okey_i_have_8_tashes{}, From, ?STATE_PLAYING = StateName, StateData) ->
  471. do_game_action(SeatNum, i_have_8_tashes, From, StateName, StateData);
  472. do_action(_SeatNum, #okey_i_have_8_tashes{}, _From, StateName, StateData) ->
  473. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  474. do_action(SeatNum, #okey_i_saw_okey{}, From, ?STATE_PLAYING = StateName, StateData) ->
  475. do_game_action(SeatNum, see_okey, From, StateName, StateData);
  476. do_action(_SeatNum, #okey_i_saw_okey{}, _From, StateName, StateData) ->
  477. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  478. do_action(SeatNum, #okey_take{pile = 0}, From, ?STATE_PLAYING = StateName, StateData) ->
  479. do_game_action(SeatNum, take_from_table, From, StateName, StateData);
  480. do_action(_SeatNum, #okey_take{pile = 0}, _From, StateName, StateData) ->
  481. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  482. do_action(SeatNum, #okey_take{pile = 1}, From, ?STATE_PLAYING = StateName, StateData) ->
  483. do_game_action(SeatNum, take_from_discarded, From, StateName, StateData);
  484. do_action(_SeatNum, #okey_take{pile = 1}, _From, StateName, StateData) ->
  485. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  486. do_action(SeatNum, #okey_discard{tile = ExtTash}, From, ?STATE_PLAYING = StateName, StateData) ->
  487. Tash = ext_to_tash(ExtTash),
  488. do_game_action(SeatNum, {discard, Tash}, From, StateName, StateData);
  489. do_action(_SeatNum, #okey_discard{}, _From, StateName, StateData) ->
  490. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  491. do_action(SeatNum, #okey_reveal{discarded = ExtDiscarded, hand = ExtHand}, From,
  492. ?STATE_PLAYING = StateName, StateData) ->
  493. Discarded = ext_to_tash(ExtDiscarded),
  494. Hand = [[if ExtTash == null -> null;
  495. true -> ext_to_tash(ExtTash)
  496. end || ExtTash <- Row] || Row <- ExtHand],
  497. DeskState = StateData#okey_state.desk_state,
  498. Gosterme = DeskState#desk_state.gosterge,
  499. {Revealed,_,_} = ?SCORING:check_reveal(Hand,Gosterme),
  500. case Revealed of
  501. true -> do_game_action(SeatNum, {reveal, Discarded, Hand}, From, StateName, StateData);
  502. false -> do_game_action(SeatNum, wrong_reveal, From, StateName, StateData)
  503. end;
  504. do_action(_SeatNum, #okey_reveal{}, _From, StateName, StateData) ->
  505. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  506. do_action(SeatNum, #okey_challenge{challenge = Challenge}, From,
  507. ?STATE_REVEAL_CONFIRMATION = StateName, #okey_state{reveal_confirmation_list = CList,
  508. wait_list = WL,
  509. timeout_timer = TRef} = StateData) ->
  510. case lists:member(SeatNum, WL) of
  511. true ->
  512. Confirmed = not Challenge,
  513. NewCList = [{SeatNum, Confirmed} | CList],
  514. NewWL = lists:delete(SeatNum, WL),
  515. if NewWL == [] ->
  516. gen_fsm:reply(From, ok),
  517. erlang:cancel_timer(TRef),
  518. finalize_round(StateData#okey_state{timeout_timer = undefined,
  519. reveal_confirmation_list = NewCList});
  520. true ->
  521. {reply, ok, StateName,
  522. StateData#okey_state{reveal_confirmation_list = NewCList,
  523. wait_list = NewWL}}
  524. end;
  525. false ->
  526. {reply, {error, not_your_turn}, StateName, StateData}
  527. end;
  528. do_action(_SeatNum, #okey_challenge{}, _From, StateName, StateData) ->
  529. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  530. do_action(_SeatNum, #okey_ready{}, _From, StateName, StateData) ->
  531. {reply, ok, StateName, StateData};
  532. do_action(_SeatNum, _UnsupportedAction, _From, StateName, StateData) ->
  533. {reply, {error, unsupported}, StateName, StateData}.
  534. %%===================================================================
  535. do_timeout_moves(#okey_state{desk_rule_pid = Desk, desk_state = DeskState} = StateData) ->
  536. #desk_state{cur_seat = CurSeatNum,
  537. hands = Hands,
  538. state = DeskStateName} = DeskState,
  539. case DeskStateName of
  540. state_take ->
  541. {ok,Events1} = desk_player_action(Desk, CurSeatNum, take_from_table),
  542. [Tash] = [Tash || {taked_from_table, S, Tash} <- Events1, S==CurSeatNum],
  543. {ok, Events2} = desk_player_action(Desk, CurSeatNum, {discard, Tash}),
  544. Events2_1 = [case E of
  545. {tash_discarded, SeatNum, Tash} ->
  546. {tash_discarded_timeout, SeatNum, Tash};
  547. _ -> E
  548. end || E <- Events2],
  549. Events = Events1 ++ [{auto_take_discard, CurSeatNum, Tash}] ++ Events2_1,
  550. process_game_events(Events, StateData);
  551. state_discard ->
  552. {_, [Tash | _]} = lists:keyfind(CurSeatNum, 1, Hands),
  553. {ok,Events1} = desk_player_action(Desk, CurSeatNum, {discard, Tash}),
  554. Events1_1 = [case E of
  555. {tash_discarded, SeatNum, Tash} ->
  556. {tash_discarded_timeout, SeatNum, Tash};
  557. _ -> E
  558. end || E <- Events1],
  559. Events = [{auto_discard, CurSeatNum, Tash} | Events1_1],
  560. process_game_events(Events, StateData)
  561. end.
  562. %%===================================================================
  563. do_game_action(SeatNum, GameAction, From, StateName,
  564. #okey_state{desk_rule_pid = Desk} = StateData) ->
  565. gas:info(?MODULE,"OKEY_NG_TABLE_TRN do_game_action SeatNum: ~p GameAction: ~p", [SeatNum, GameAction]),
  566. case desk_player_action(Desk, SeatNum, GameAction) of
  567. {ok, Events} ->
  568. Response = case GameAction of
  569. i_have_gosterge ->
  570. true;
  571. i_have_8_tashes ->
  572. true;
  573. take_from_table ->
  574. [Tash] = [Tash || {taked_from_table, S, Tash} <- Events, S==SeatNum],
  575. tash_to_ext(Tash);
  576. take_from_discarded ->
  577. [Tash] = [Tash || {taked_from_discarded, S, Tash} <- Events, S==SeatNum],
  578. tash_to_ext(Tash);
  579. _ -> ok
  580. end,
  581. gen_fsm:reply(From, Response),
  582. process_game_events(Events, StateData);
  583. {error, Reason} ->
  584. wf:info(?MODULE,"do game action error: ~p",[{Reason,GameAction}]),
  585. ExtError = desk_error_to_ext(Reason),
  586. {reply, ExtError, StateName, StateData}
  587. end.
  588. process_game_events(Events, #okey_state{desk_state = DeskState, players = Players,
  589. relay = Relay, timeout_timer = OldTRef,
  590. round_timeout = RoundTimeout, round_timer = RoundTRef,
  591. turn_timeout = TurnTimeout} = StateData) ->
  592. NewDeskState = handle_desk_events(Events, DeskState, Players, Relay, StateData), %% Track the desk and send game events to clients
  593. #desk_state{state = DeskStateName} = NewDeskState,
  594. case DeskStateName of
  595. state_finished ->
  596. if is_integer(RoundTimeout) -> erlang:cancel_timer(RoundTRef); true -> do_nothing end,
  597. erlang:cancel_timer(OldTRef),
  598. on_game_finish(StateData#okey_state{desk_state = NewDeskState});
  599. state_take ->
  600. case [E || {next_player, _, _} = E <- Events] of %% Find a next player event
  601. [] ->
  602. {next_state, ?STATE_PLAYING, StateData#okey_state{desk_state = NewDeskState}};
  603. [_|_] ->
  604. erlang:cancel_timer(OldTRef),
  605. {Magic, TRef} = start_timer(TurnTimeout),
  606. {next_state, ?STATE_PLAYING, StateData#okey_state{desk_state = NewDeskState,
  607. timeout_timer = TRef,
  608. timeout_magic = Magic}}
  609. end;
  610. state_discard ->
  611. {next_state, ?STATE_PLAYING, StateData#okey_state{desk_state = NewDeskState}}
  612. end.
  613. on_game_finish(#okey_state{desk_state = DeskState,
  614. reveal_confirmation = RevealConfirmation,
  615. reveal_confirmation_timeout = Timeout} = StateData) ->
  616. #desk_state{finish_reason = FinishReason,
  617. finish_info = FinishInfo,
  618. hands = Hands} = DeskState,
  619. if FinishReason == reveal andalso RevealConfirmation ->
  620. {Revealer, _Tashes, _Discarded} = FinishInfo,
  621. WL = [SeatNum || {SeatNum, _} <- Hands, SeatNum =/= Revealer],
  622. {Magic, TRef} = start_timer(Timeout),
  623. {next_state, ?STATE_REVEAL_CONFIRMATION,
  624. StateData#okey_state{reveal_confirmation_list = [],
  625. wait_list = WL,
  626. timeout_timer = TRef,
  627. timeout_magic = Magic}};
  628. true ->
  629. finalize_round(StateData)
  630. end.
  631. %%===================================================================
  632. finalize_round(#okey_state{desk_state = #desk_state{finish_reason = FinishReason,
  633. finish_info = FinishInfo,
  634. hands = Hands,
  635. gosterge = Gosterge,
  636. has_gosterge = WhoHasGosterge,
  637. have_8_tashes = Have8Tashes},
  638. scoring_state = ScoringState,
  639. reveal_confirmation = RevealConfirmation,
  640. reveal_confirmation_list = CList,
  641. parent = Parent, players = Players,
  642. game_id = GameId, table_id = TableId} = StateData) ->
  643. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Finalizing the round. Finish reason: ~p. Finish info: ~p.",
  644. [GameId, TableId, FinishReason, FinishInfo]),
  645. FR = case FinishReason of
  646. tashes_out -> tashes_out;
  647. timeout -> timeout;
  648. set_timeout -> set_timeout;
  649. reveal ->
  650. {Revealer, Tashes, Discarded} = FinishInfo,
  651. ConfirmationList = if RevealConfirmation -> CList; true -> [] end,
  652. CListUId = [{SeatNum, get_user_id_by_seat_num(SeatNum, Players), Response}
  653. || {SeatNum, Response} <- ConfirmationList],
  654. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Confirmation list: ~p.", [GameId, TableId, CListUId]),
  655. {reveal, Revealer, Tashes, Discarded, ConfirmationList};
  656. gosterge_finish ->
  657. Winner = FinishInfo,
  658. {gosterge_finish, Winner}
  659. end,
  660. {NewScoringState, GameOver} = ?SCORING:round_finished(ScoringState, FR, Hands, Gosterge,
  661. WhoHasGosterge, Have8Tashes),
  662. {_, RoundScore, _, TotalScore} = ?SCORING:last_round_result(NewScoringState),
  663. RoundScorePl = [{get_player_id_by_seat_num(SeatNum, Players), Points} || {SeatNum, Points} <- RoundScore],
  664. TotalScorePl = [{get_player_id_by_seat_num(SeatNum, Players), Points} || {SeatNum, Points} <- TotalScore],
  665. if GameOver ->
  666. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Set is over.", [GameId, TableId]),
  667. parent_send_game_res(Parent, TableId, NewScoringState, RoundScorePl, TotalScorePl),
  668. {next_state, ?STATE_SET_FINISHED, StateData#okey_state{scoring_state = NewScoringState}};
  669. true ->
  670. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Round is over.", [GameId, TableId]),
  671. parent_send_round_res(Parent, TableId, NewScoringState, RoundScorePl, TotalScorePl),
  672. {next_state, ?STATE_FINISHED, StateData#okey_state{scoring_state = NewScoringState}}
  673. end.
  674. %% handle_desk_events(Events, DeskState, Players) -> NextStateData
  675. %% Tracks the desk state and sends events to clients
  676. handle_desk_events([], DeskState, _Players, _Relay, _StateData) ->
  677. DeskState;
  678. handle_desk_events([Event | Events], DeskState, Players, Relay, #okey_state{} = StateData) ->
  679. #desk_state{cur_seat = CurSeatNum,
  680. hands = Hands,
  681. discarded = Discarded,
  682. deck = Deck,
  683. have_8_tashes = Have8Tashes} = DeskState,
  684. NewDeskState =
  685. case Event of
  686. {has_gosterge, SeatNum} ->
  687. Msg = create_okey_player_has_gosterge(SeatNum, Players),
  688. relay_publish_ge(Relay, Msg, StateData),
  689. DeskState#desk_state{has_gosterge = SeatNum};
  690. {has_8_tashes, SeatNum, Value} ->
  691. Msg = create_okey_player_has_8_tashes(SeatNum, Value, Players),
  692. relay_publish_ge(Relay, Msg, StateData),
  693. DeskState#desk_state{have_8_tashes = [SeatNum | Have8Tashes]};
  694. {saw_okey, SeatNum} ->
  695. Msg = create_okey_disable_okey(SeatNum, CurSeatNum, Players),
  696. relay_publish_ge(Relay, Msg, StateData),
  697. DeskState;
  698. {taked_from_discarded, SeatNum, Tash} ->
  699. PrevSeatNum = prev_seat_num(SeatNum),
  700. {_, [Tash | NewPile]} = lists:keyfind(PrevSeatNum, 1, Discarded),
  701. Msg = create_okey_tile_taken_discarded(SeatNum, Tash, length(NewPile), Players),
  702. relay_publish_ge(Relay, Msg, StateData),
  703. NewDiskarded = lists:keyreplace(PrevSeatNum, 1, Discarded, {PrevSeatNum, NewPile}),
  704. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  705. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, [Tash | Hand]}),
  706. DeskState#desk_state{hands = NewHands, discarded = NewDiskarded, state = state_discard};
  707. {taked_from_table, SeatNum, Tash} ->
  708. [Tash | NewDeck] = Deck,
  709. [ send_to_client_ge(Relay, Id,
  710. create_okey_tile_taken_table(CSN, CurSeatNum, Tash, length(NewDeck), Players), StateData)
  711. || #player{id = Id,seat_num = CSN} <- find_connected_players(Players) ],
  712. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  713. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, [Tash | Hand]}),
  714. DeskState#desk_state{hands = NewHands, deck = NewDeck, state = state_discard};
  715. {tash_discarded, SeatNum, Tash} ->
  716. Msg = create_okey_tile_discarded(SeatNum, Tash, false, Players),
  717. relay_publish_ge(Relay, Msg, StateData),
  718. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  719. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, lists:delete(Tash, Hand)}),
  720. {_, Pile} = lists:keyfind(SeatNum, 1, Discarded),
  721. NewDiscarded = lists:keyreplace(SeatNum, 1, Discarded, {SeatNum, [Tash | Pile]}),
  722. DeskState#desk_state{hands = NewHands, discarded = NewDiscarded, state = state_take};
  723. {tash_discarded_timeout, SeatNum, Tash} -> %% Injected event
  724. Msg = create_okey_tile_discarded(SeatNum, Tash, true, Players),
  725. relay_publish_ge(Relay, Msg, StateData),
  726. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  727. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, lists:delete(Tash, Hand)}),
  728. {_, Pile} = lists:keyfind(SeatNum, 1, Discarded),
  729. NewDiscarded = lists:keyreplace(SeatNum, 1, Discarded, {SeatNum, [Tash | Pile]}),
  730. DeskState#desk_state{hands = NewHands, discarded = NewDiscarded, state = state_take};
  731. {auto_take_discard, SeatNum, Tash} -> %% Injected event
  732. #player{id = PlayerId, user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  733. Msg = create_okey_turn_timeout(UserId, Tash, Tash),
  734. send_to_client_ge(Relay, PlayerId, Msg, StateData),
  735. DeskState;
  736. {auto_discard, SeatNum, Tash} -> %% Injected event
  737. #player{id = PlayerId, user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  738. Msg = create_okey_turn_timeout(UserId, null, Tash),
  739. send_to_client_ge(Relay, PlayerId, Msg, StateData),
  740. DeskState;
  741. {next_player, SeatNum, EnableOkey} ->
  742. #player{id = PlayerId, user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  743. case EnableOkey of
  744. true ->
  745. MsgOkey = create_okey_enabled(SeatNum, CurSeatNum, Players),
  746. send_to_client_ge(Relay, PlayerId, MsgOkey, StateData);
  747. false -> skip
  748. end,
  749. Msg = create_okey_next_turn(SeatNum, Players),
  750. relay_publish_ge(Relay, Msg, StateData),
  751. DeskState#desk_state{cur_seat = SeatNum, state = state_take};
  752. no_winner_finish ->
  753. DeskState#desk_state{state = state_finished,
  754. finish_reason = tashes_out};
  755. {wrong_reveal,SeatNum} ->
  756. #player{id = PlayerId, user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  757. Msg = create_deny_wrong_reveal(),
  758. send_to_client_ge(Relay, PlayerId, Msg, StateData),
  759. DeskState;
  760. {reveal, SeatNum, RevealedTashes, DiscardedTash} ->
  761. #player{id = PlayerId, user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  762. Msg = create_okey_revealed(SeatNum, DiscardedTash, RevealedTashes, Players),
  763. relay_publish_ge(Relay, Msg, StateData),
  764. DeskState#desk_state{state = state_finished,
  765. finish_reason = reveal,
  766. finish_info = {SeatNum, RevealedTashes, DiscardedTash}};
  767. {gosterge_finish, SeatNum} ->
  768. DeskState#desk_state{state = state_finished,
  769. finish_reason = gosterge_finish,
  770. finish_info = SeatNum}
  771. end,
  772. handle_desk_events(Events, NewDeskState, Players, Relay, StateData).
  773. create_deny_wrong_reveal() -> #wrong_reveal{}.
  774. %%===================================================================
  775. init_scoring(GameType, PlayersInfo, Rounds) ->
  776. SeatsInfo = [{SeatNum, Points} || {_PlayerId, _UserInfo, SeatNum, Points} <- PlayersInfo],
  777. ?SCORING:init(GameType, SeatsInfo, Rounds).
  778. %% start_timer(Timeout) -> {Magic, TRef}
  779. start_timer(Timeout) ->
  780. Magic = make_ref(),
  781. TRef = erlang:send_after(Timeout, self(), {timeout, Magic}),
  782. {Magic, TRef}.
  783. %% players_init() -> players()
  784. players_init() ->
  785. midict:new().
  786. %% reg_player(PlayerId, SeatNum, UserId, IsBot, Players) -> NewPlayers
  787. reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, Connected, Players) ->
  788. store_player_rec(#player{id =PlayerId, seat_num = SeatNum, user_id = UserId,
  789. is_bot = IsBot, info = UserInfo, connected = Connected}, Players).
  790. %% reg_player(#player{}, Players) -> NewPlayers
  791. store_player_rec(#player{id =Id, seat_num = SeatNum, user_id = UserId,
  792. is_bot = IsBot, connected = Connected} = Player, Players) ->
  793. Indices = [{seat_num, SeatNum}, {user_id, UserId}, {is_bot, IsBot}, {connected, Connected}],
  794. midict:store(Id, Player, Indices, Players).
  795. %% get_player_id_by_seat_num(SeatNum, Players) -> PlayerId
  796. get_player_id_by_seat_num(SeatNum, Players) ->
  797. [#player{id = PlayerId}] = midict:geti(SeatNum, seat_num, Players),
  798. PlayerId.
  799. %% get_user_id_by_seat_num(SeatNum, Players) -> PlayerId
  800. get_user_id_by_seat_num(SeatNum, Players) ->
  801. [#player{user_id = UserId}] = midict:geti(SeatNum, seat_num, Players),
  802. UserId.
  803. %% fetch_player(PlayerId, Players) -> Player
  804. fetch_player(PlayerId, Players) ->
  805. midict:fetch(PlayerId, Players).
  806. %% get_player(PlayerId, Players) -> {ok, Player} | error
  807. get_player(PlayerId, Players) ->
  808. midict:find(PlayerId, Players).
  809. %% get_player_by_seat_num(SeatNum, Players) -> Player
  810. get_player_by_seat_num(SeatNum, Players) ->
  811. [Player] = midict:geti(SeatNum, seat_num, Players),
  812. Player.
  813. %% find_players_by_seat_num(SeatNum, Players) -> [Player]
  814. find_players_by_seat_num(SeatNum, Players) ->
  815. midict:geti(SeatNum, seat_num, Players).
  816. %% find_connected_players(Players) -> [Player]
  817. find_connected_players(Players) ->
  818. midict:geti(true, connected, Players).
  819. %% del_player(PlayerId, Players) -> NewPlayers
  820. del_player(PlayerId, Players) ->
  821. midict:erase(PlayerId, Players).
  822. %% players_to_list(Players) -> List
  823. players_to_list(Players) ->
  824. midict:all_values(Players).
  825. %% @spec init_players(PlayersInfo) -> Players
  826. %% @end
  827. %% PlayersInfo = [{PlayerId, UserInfo, SeatNum, StartPoints}]
  828. init_players(PlayersInfo) ->
  829. init_players(PlayersInfo, players_init()).
  830. init_players([], Players) ->
  831. Players;
  832. init_players([{PlayerId, UserInfo, SeatNum, _StartPoints} | PlayersInfo], Players) ->
  833. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  834. NewPlayers = reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, _Connected = false, Players),
  835. init_players(PlayersInfo, NewPlayers).
  836. %%=================================================================
  837. handle_log(User,#game_event{}=Event,
  838. #okey_state{game_id=GameId,tournament_type=GameKind,game_mode=GameMode,speed=Speed,rounds=Rounds}=State) ->
  839. ProtocolEvent = #protocol_event{feed_id=User,module=GameKind,speed=Speed,rounds=Rounds,user=User,
  840. type=GameMode,id=?GAME_STATS:timestamp(),event=Event#game_event.event,game_event=Event},
  841. ?GAME_STATS:update_stats(User,ProtocolEvent,#protocol_event.event,State).
  842. send_to_subscriber_ge(Relay, SubscrId, Msg, #okey_state{players=Players,game_id = GameId} = State) ->
  843. [Name|List] = tuple_to_list(Msg),
  844. Event = #game_event{game = GameId, event = Name, args = lists:zip(known_records:fields(Name),List) },
  845. % gas:info(?MODULE,"SUBSCRIBER ~p",[SubscrId]),
  846. ?RELAY:table_message(Relay, {to_subscriber, SubscrId, Event}).
  847. send_to_client_ge(Relay, PlayerId, Msg, #okey_state{players=Players,game_id = GameId} = State) ->
  848. [Name|List] = tuple_to_list(Msg),
  849. Event = #game_event{game = GameId, event = Name, args = lists:zip(known_records:fields(Name),List) },
  850. % gas:info(?MODULE,"SEND CLIENT ~p",[Event]),
  851. ?GAME_STATS:protocol_event(table,Event,State),
  852. case get_player(PlayerId, Players) of
  853. {ok, #player{user_id=User,is_bot=false}} -> handle_log(User,Event,State);
  854. _ -> skip end,
  855. ?RELAY:table_message(Relay, {to_client, PlayerId, Event}).
  856. relay_publish_ge(Relay, Msg, #okey_state{players=Players,game_id = GameId} = State) ->
  857. [Name|List] = tuple_to_list(Msg),
  858. Event = #game_event{game = GameId, event = Name, args = lists:zip(known_records:fields(Name),List) },
  859. % gas:info(?MODULE,"RELAYX PUBLISH ~p",[Event]),
  860. ?GAME_STATS:protocol_event(table,Event,State),
  861. [ handle_log(UserId,Event,State)
  862. || {_,#player{id=Id,user_id=UserId,is_bot=false},_} <- midict:to_list(Players)],
  863. relay_publish(Relay, Event).
  864. relay_publish(Relay, Msg) ->
  865. ?RELAY:table_message(Relay, {publish, Msg}).
  866. relay_allow_broadcast_for_player(Relay, PlayerId) ->
  867. ?RELAY:table_message(Relay, {allow_broadcast_for_player, PlayerId}).
  868. relay_register_player(Relay, UserId, PlayerId) ->
  869. ?RELAY:table_request(Relay, {register_player, UserId, PlayerId}).
  870. relay_unregister_player(Relay, PlayerId, Reason) ->
  871. ?RELAY:table_request(Relay, {unregister_player, PlayerId, Reason}).
  872. relay_kick_player(Relay, PlayerId) ->
  873. ?RELAY:table_request(Relay, {kick_player, PlayerId}).
  874. relay_stop(Relay) ->
  875. ?RELAY:table_message(Relay, stop).
  876. parent_confirm_registration({ParentMod, ParentPid}, TableId, RequestId) ->
  877. ParentMod:table_message(ParentPid, TableId, {response, RequestId, ok}).
  878. parent_confirm_replacement({ParentMod, ParentPid}, TableId, RequestId) ->
  879. ParentMod:table_message(ParentPid, TableId, {response, RequestId, ok}).
  880. parent_notify_table_created({ParentMod, ParentPid}, TableId, RelayPid) ->
  881. ParentMod:table_message(ParentPid, TableId, {table_created, {?RELAY, RelayPid}}).
  882. parent_send_round_res({ParentMod, ParentPid}, TableId, ScoringState, RoundScores, TotalScores) ->
  883. ParentMod:table_message(ParentPid, TableId, {round_finished, ScoringState, RoundScores, TotalScores}).
  884. parent_send_game_res({ParentMod, ParentPid}, TableId, ScoringState, RoundScores, TotalScores) ->
  885. ParentMod:table_message(ParentPid, TableId, {game_finished, ScoringState, RoundScores, TotalScores}).
  886. parent_send_player_connected({ParentMod, ParentPid}, TableId, PlayerId) ->
  887. ParentMod:table_message(ParentPid, TableId, {player_connected, PlayerId}).
  888. parent_send_player_disconnected({ParentMod, ParentPid}, TableId, PlayerId) ->
  889. ParentMod:table_message(ParentPid, TableId, {player_disconnected, PlayerId}).
  890. desk_player_action(Desk, SeatNum, Action) ->
  891. ?DESK:player_action(Desk, SeatNum, Action).
  892. %%===================================================================
  893. create_okey_game_info(#okey_state{table_name = TName, mult_factor = MulFactor,
  894. slang_flag = SlangFlag, observer_flag = ObserverFlag,
  895. speed = Speed, turn_timeout = TurnTimeout,
  896. reveal_confirmation_timeout = RevealConfirmationTimeout,
  897. ready_timeout = ReadyTimeout, game_mode = GameMode,
  898. rounds = Rounds1, players = Players, tour = Tour,
  899. tours = Tours, gosterge_finish_allowed = GostergeFinish,
  900. tournament_type = TournamentType, pause_mode = PauseMode,
  901. social_actions_enabled = SocialActionsEnabled,
  902. next_series_confirmation = ConfirmMode}) ->
  903. PInfos = [case find_players_by_seat_num(SeatNum, Players) of
  904. [#player{info = UserInfo}] -> UserInfo;
  905. [] -> null
  906. end || SeatNum <- lists:seq(1, ?SEATS_NUM)],
  907. Timeouts = #'OkeyTimeouts'{speed = Speed,
  908. turn_timeout = TurnTimeout,
  909. challenge_timeout = RevealConfirmationTimeout,
  910. ready_timeout = ReadyTimeout,
  911. rematch_timeout = ?REMATCH_TIMEOUT},
  912. Sets = if Tours == undefined -> null; true -> Tours end,
  913. SetNo = if Tour == undefined -> null; true -> Tour end,
  914. Rounds = if Rounds1 == infinity -> -1; true -> Rounds1 end,
  915. #okey_game_info{table_name = list_to_binary(TName),
  916. players = PInfos,
  917. timeouts = Timeouts,
  918. game_type = GameMode,
  919. finish_with_gosterge = GostergeFinish,
  920. rounds = Rounds,
  921. sets = Sets,
  922. set_no = SetNo,
  923. mul_factor = MulFactor,
  924. slang_flag = SlangFlag,
  925. observer_flag = ObserverFlag,
  926. pause_enabled = PauseMode == normal,
  927. social_actions_enabled = SocialActionsEnabled,
  928. tournament_type = TournamentType,
  929. series_confirmation_mode = list_to_binary(atom_to_list(ConfirmMode))
  930. }.
  931. create_okey_game_player_state(_PlayerId, ?STATE_WAITING_FOR_START,
  932. #okey_state{cur_round = CurRound, scoring_state = ScoringState,
  933. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  934. Chanak = ?SCORING:chanak(ScoringState),
  935. SetTimeout = if SetTimeout1 == infinity -> null;
  936. true -> calc_timeout_comp(SetTRef, 2000)
  937. end,
  938. #okey_game_player_state{whos_move = null,
  939. game_state = game_initializing,
  940. piles = null,
  941. tiles = null,
  942. gosterge = null,
  943. pile_height = null,
  944. current_round = CurRound,
  945. next_turn_in = 0,
  946. paused = false,
  947. chanak_points = Chanak,
  948. round_timeout = null,
  949. set_timeout = SetTimeout};
  950. create_okey_game_player_state(PlayerId, ?STATE_PLAYING,
  951. #okey_state{timeout_timer = TRef, cur_round = CurRound,
  952. players = Players, desk_state = DeskState,
  953. scoring_state = ScoringState, round_timer = RoundTRef,
  954. round_timeout = RoundTimeout1, set_timer = SetTRef,
  955. set_timeout = SetTimeout1}) ->
  956. #player{seat_num = SeatNum} = fetch_player(PlayerId, Players),
  957. #desk_state{state = DeskStateName,
  958. hands = Hands,
  959. discarded = Discarded,
  960. gosterge = Gosterge,
  961. deck = DeskDeck,
  962. cur_seat = CurSeatNum} = DeskState,
  963. {_, PlayerHand} = lists:keyfind(SeatNum, 1, Hands),
  964. Hand = [tash_to_ext(Tash) || Tash <- PlayerHand],
  965. #player{user_id = CurUserId} = get_player_by_seat_num(CurSeatNum, Players),
  966. Timeout = calc_timeout(TRef),
  967. F = fun(_, N) ->
  968. #player{user_id = UserId} = get_player_by_seat_num(N, Players),
  969. {_, Tahes} = lists:keyfind(N, 1, Discarded),
  970. {{UserId, [tash_to_ext(Tash) || Tash <- Tahes]}, next_seat_num(N)}
  971. end,
  972. {Piles, _} = lists:mapfoldl(F, prev_seat_num(SeatNum), lists:seq(1, ?SEATS_NUM)),
  973. GameState = statename_to_api_string(DeskStateName),
  974. Chanak = ?SCORING:chanak(ScoringState),
  975. RoundTimeout = if RoundTimeout1 == infinity -> null;
  976. true -> calc_timeout_comp(RoundTRef, 2000)
  977. end,
  978. SetTimeout = if SetTimeout1 == infinity -> null;
  979. true -> calc_timeout_comp(SetTRef, 2000)
  980. end,
  981. #okey_game_player_state{whos_move = CurUserId,
  982. game_state = GameState,
  983. piles = Piles,
  984. tiles = Hand,
  985. gosterge = tash_to_ext(Gosterge),
  986. pile_height = length(DeskDeck),
  987. current_round = CurRound,
  988. next_turn_in = Timeout,
  989. paused = false,
  990. chanak_points = Chanak,
  991. round_timeout = RoundTimeout,
  992. set_timeout = SetTimeout};
  993. create_okey_game_player_state(PlayerId, ?STATE_REVEAL_CONFIRMATION,
  994. #okey_state{timeout_timer = TRef, cur_round = CurRound,
  995. players = Players, desk_state = DeskState,
  996. scoring_state = ScoringState,
  997. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  998. #player{seat_num = SeatNum} = fetch_player(PlayerId, Players),
  999. #desk_state{hands = Hands,
  1000. discarded = Discarded,
  1001. gosterge = Gosterge,
  1002. deck = DeskDeck,
  1003. cur_seat = CurSeatNum} = DeskState,
  1004. {_, PlayerHand} = lists:keyfind(SeatNum, 1, Hands),
  1005. Hand = [tash_to_ext(Tash) || Tash <- PlayerHand],
  1006. #player{user_id = CurUserId} = get_player_by_seat_num(CurSeatNum, Players),
  1007. Timeout = calc_timeout(TRef),
  1008. F = fun(_, N) ->
  1009. Pile = case lists:keyfind(N, 1, Discarded) of
  1010. {_, []} -> null;
  1011. {_, [Tash|_]} -> tash_to_ext(Tash)
  1012. end,
  1013. {Pile, next_seat_num(N)}
  1014. end,
  1015. {Piles, _} = lists:mapfoldl(F, prev_seat_num(SeatNum), lists:seq(1, ?SEATS_NUM)),
  1016. Chanak = ?SCORING:chanak(ScoringState),
  1017. SetTimeout = if SetTimeout1 == infinity -> null;
  1018. true -> calc_timeout_comp(SetTRef, 2000)
  1019. end,
  1020. #okey_game_player_state{whos_move = CurUserId,
  1021. game_state = do_okey_challenge,
  1022. piles = Piles,
  1023. tiles = Hand,
  1024. gosterge = tash_to_ext(Gosterge),
  1025. pile_height = length(DeskDeck),
  1026. current_round = CurRound,
  1027. next_turn_in = Timeout,
  1028. paused = false,
  1029. chanak_points = Chanak,
  1030. round_timeout = null,
  1031. set_timeout = SetTimeout};
  1032. create_okey_game_player_state(_PlayerId, ?STATE_FINISHED,
  1033. #okey_state{cur_round = CurRound, scoring_state = ScoringState,
  1034. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  1035. Chanak = ?SCORING:chanak(ScoringState),
  1036. SetTimeout = if SetTimeout1 == infinity -> null;
  1037. true -> calc_timeout_comp(SetTRef, 2000)
  1038. end,
  1039. #okey_game_player_state{whos_move = null,
  1040. game_state = game_initializing,
  1041. piles = null,
  1042. tiles = null,
  1043. gosterge = null,
  1044. pile_height = null,
  1045. current_round = CurRound,
  1046. next_turn_in = 0,
  1047. paused = false,
  1048. chanak_points = Chanak,
  1049. round_timeout = null,
  1050. set_timeout = SetTimeout};
  1051. create_okey_game_player_state(PlayerId, ?STATE_PAUSE,
  1052. #okey_state{paused_statename = PausedStateName,
  1053. paused_timeout_value = Timeout
  1054. } = StateData) ->
  1055. Msg = create_okey_game_player_state(PlayerId, PausedStateName, StateData),
  1056. Msg#okey_game_player_state{next_turn_in = Timeout,
  1057. paused = true}.
  1058. create_okey_game_started(SeatNum, DeskState, CurRound,
  1059. #okey_state{scoring_state = ScoringState, round_timeout = RoundTimeout1,
  1060. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  1061. Chanak = ?SCORING:chanak(ScoringState),
  1062. #desk_state{hands = Hands,
  1063. gosterge = Gosterge,
  1064. deck = DeskDeck} = DeskState,
  1065. {_, PlayerHand} = lists:keyfind(SeatNum, 1, Hands),
  1066. Hand = [tash_to_ext(Tash) || Tash <- PlayerHand],
  1067. RoundTimeout = if RoundTimeout1 == infinity -> null;
  1068. true -> RoundTimeout1 - 2000
  1069. end,
  1070. SetTimeout = if SetTimeout1 == infinity -> null;
  1071. true -> calc_timeout_comp(SetTRef, 2000)
  1072. end,
  1073. #okey_game_started{tiles = Hand,
  1074. gosterge = tash_to_ext(Gosterge),
  1075. pile_height = length(DeskDeck),
  1076. current_round = CurRound,
  1077. current_set = 1, %% XXX Concept of sets is deprecated
  1078. chanak_points = Chanak,
  1079. round_timeout = RoundTimeout,
  1080. set_timeout = SetTimeout}.
  1081. create_okey_next_turn(CurSeat, Players) ->
  1082. #player{user_id = UserId} = get_player_by_seat_num(CurSeat, Players),
  1083. #okey_next_turn{player = UserId}.
  1084. create_player_left(SeatNum, UserInfo, Players) ->
  1085. #player{user_id = OldUserId} = get_player_by_seat_num(SeatNum, Players),
  1086. IsBot = UserInfo#'PlayerInfo'.robot,
  1087. #player_left{player = OldUserId,
  1088. human_replaced = not IsBot,
  1089. replacement = UserInfo}.
  1090. create_okey_player_has_gosterge(SeatNum, Players) ->
  1091. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1092. #okey_player_has_gosterge{player = UserId}.
  1093. create_okey_player_has_8_tashes(SeatNum, Value, Players) ->
  1094. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1095. #okey_player_has_8_tashes{player = UserId,
  1096. value = Value}.
  1097. create_okey_enabled(SeatNum, CurSeatNum, Players) ->
  1098. #player{user_id = Who} = get_player_by_seat_num(SeatNum, Players),
  1099. #player{user_id = Whom} = get_player_by_seat_num(CurSeatNum, Players),
  1100. #okey_enable{player = Whom,
  1101. enabled = true,
  1102. who = Who}.
  1103. create_okey_disable_okey(SeatNum, CurSeatNum, Players) ->
  1104. #player{user_id = Who} = get_player_by_seat_num(SeatNum, Players),
  1105. #player{user_id = Whom} = get_player_by_seat_num(CurSeatNum, Players),
  1106. #okey_enable{player = Whom,
  1107. enabled = false,
  1108. who = Who}.
  1109. create_okey_tile_taken_discarded(SeatNum, Tash, PileHeight, Players) ->
  1110. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1111. #okey_tile_taken{player = UserId,
  1112. pile = 1, %% From discarded tashes of the previous player
  1113. revealed = tash_to_ext(Tash),
  1114. pile_height = PileHeight}.
  1115. create_okey_tile_taken_table(CSN, SeatNum, Tash, PileHeight, Players) ->
  1116. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1117. #okey_tile_taken{player = UserId,
  1118. pile = 0, %% From the deck on the table
  1119. revealed = case CSN == SeatNum of true -> tash_to_ext(Tash); _ -> null end,
  1120. pile_height = PileHeight}.
  1121. create_okey_tile_discarded(SeatNum, Tash, Timeouted, Players) ->
  1122. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1123. #okey_tile_discarded{player = UserId,
  1124. tile = tash_to_ext(Tash),
  1125. timeouted = Timeouted}.
  1126. % OKEY GAME RESULTS
  1127. round_results(
  1128. Reason,
  1129. Revealer, RevealerWin, WrongRejects, RoundScore,
  1130. TotalScore, PlayersAchsPoints,
  1131. State=#okey_state{
  1132. tournament_type=GameKind,
  1133. game_mode=GameMode,
  1134. speed=Speed,
  1135. cur_round=Round,
  1136. rounds=Rounds,
  1137. game_id=GameId,
  1138. players=Players}) ->
  1139. wf:info(?MODULE,"Score ~p/~p",[RoundScore,TotalScore]),
  1140. {Date,Time} = calendar:local_time(),
  1141. Results = [begin
  1142. Player = #player{user_id = UserId,is_bot=IsBot} = get_player_by_seat_num(SeatNum, Players),
  1143. IsWinner = if SeatNum == Revealer -> RevealerWin; true -> not RevealerWin end,
  1144. GoodShot = if SeatNum == Revealer -> RevealerWin; true -> not lists:member(SeatNum, WrongRejects) end,
  1145. {_, PlayerScoreTotal} = lists:keyfind(SeatNum, 1, TotalScore),
  1146. {_, PlayerScoreRound} = lists:keyfind(SeatNum, 1, RoundScore),
  1147. RE = #reveal_event{
  1148. id = ?GAME_STATS:timestamp(),
  1149. feed_id = UserId,
  1150. user = UserId,
  1151. module = GameKind,
  1152. speed = Speed,
  1153. rounds = Rounds,
  1154. date = Date,
  1155. time = Time,
  1156. type = GameMode,
  1157. reason = Reason,
  1158. winner = IsWinner,
  1159. score = PlayerScoreRound,
  1160. total = PlayerScoreTotal},
  1161. PlayerInfo = Player#player.info,
  1162. DisplayName = wf:to_list(PlayerInfo#'PlayerInfo'.name) ++ " " ++
  1163. wf:to_list(PlayerInfo#'PlayerInfo'.surname),
  1164. case {SeatNum == Revealer,Revealer,IsBot} of
  1165. {_,none,_} -> ?GAME_STATS:reveal_event(UserId,RE,State);
  1166. {true,_,false} -> ?GAME_STATS:reveal_event(UserId,RE,State);
  1167. _ -> skip end,
  1168. {DisplayName,IsWinner,js_hack(PlayerScoreRound),js_hack(PlayerScoreTotal)}
  1169. end || SeatNum <- lists:seq(1, ?SEATS_NUM)],
  1170. #okey_round_ended{
  1171. round = Round,
  1172. reason = Reason,
  1173. results = Results,
  1174. next_action = next_round}.
  1175. js_hack(Score) when Score < 0 -> -Score * 1000000;
  1176. js_hack(Score) -> Score.
  1177. create_okey_series_ended(Results, Players, Confirm,
  1178. #okey_state{tournament_type=GameKind,game_mode=GameMode,speed=Speed,rounds=Rounds}=GameState) ->
  1179. {Date,Time} = calendar:local_time(),
  1180. [begin
  1181. #player{user_id = UserId,is_bot=IsBot} = fetch_player(PlayerId, Players),
  1182. case IsBot of
  1183. false ->
  1184. Event = #series_event{result=Status,user=UserId,date=Date,time=Time,score=Score,
  1185. speed=Speed,rounds=Rounds,feed_id={GameMode,Speed,Rounds,UserId},
  1186. id=?GAME_STATS:timestamp()},
  1187. ?GAME_STATS:series_event(UserId,Event,GameState);
  1188. _ -> skip end
  1189. end || {PlayerId, Position, Score, Status} <- Results],
  1190. #okey_series_ended{standings = Results}.
  1191. create_okey_tour_result(TurnNum, Results) ->
  1192. Records = [begin
  1193. #okey_turn_record{player_id = UserId, place = Position, score = Score,
  1194. status = Status}
  1195. end || {UserId, Position, Score, Status} <- Results],
  1196. #okey_turn_result{turn_num = TurnNum,
  1197. records = Records}.
  1198. create_okey_revealed(SeatNum, DiscardedTash, TashPlaces, Players) ->
  1199. #player{user_id = UserId, info = Player} = get_player_by_seat_num(SeatNum, Players),
  1200. TashPlacesExt = [[case T of
  1201. null -> null;
  1202. _ -> tash_to_ext(T)
  1203. end || T <- Row ] || Row <- TashPlaces],
  1204. #okey_revealed{player = wf:to_list(Player#'PlayerInfo'.name) ++ " " ++ wf:to_list(Player#'PlayerInfo'.surname),
  1205. discarded = tash_to_ext(DiscardedTash),
  1206. hand = TashPlacesExt}.
  1207. create_okey_turn_timeout(UserId, null, TashDiscarded) ->
  1208. #okey_turn_timeout{player = UserId,
  1209. tile_taken = null,
  1210. tile_discarded = tash_to_ext(TashDiscarded)};
  1211. create_okey_turn_timeout(UserId, TashTaken, TashDiscarded) ->
  1212. #okey_turn_timeout{player = UserId,
  1213. tile_taken = tash_to_ext(TashTaken),
  1214. tile_discarded = tash_to_ext(TashDiscarded)}.
  1215. create_game_paused_pause(UserId, GameId) ->
  1216. #game_paused{game = GameId,
  1217. who = UserId,
  1218. action = pause,
  1219. retries = 0}.
  1220. create_game_paused_resume(UserId, GameId) ->
  1221. #game_paused{game = GameId,
  1222. who = UserId,
  1223. action = resume,
  1224. retries = 0}.
  1225. create_okey_playing_tables(Num) ->
  1226. #okey_playing_tables{num = Num}.
  1227. tash_to_ext(false_okey) -> #'OkeyPiece'{color = 2, value = 0};
  1228. tash_to_ext({Color, Value}) -> #'OkeyPiece'{color = Color, value = Value}.
  1229. ext_to_tash(#'OkeyPiece'{color = 2, value = 0}) -> false_okey;
  1230. ext_to_tash(#'OkeyPiece'{color = Color, value = Value}) -> {Color, Value}.
  1231. %statename_to_api_string(state_wait) -> do_okey_ready;
  1232. statename_to_api_string(state_take) -> do_okey_take;
  1233. statename_to_api_string(state_discard) -> do_okey_discard;
  1234. statename_to_api_string(state_finished) -> game_finished.
  1235. desk_error_to_ext(action_disabled) -> false;
  1236. desk_error_to_ext(no_gosterge) -> false;
  1237. desk_error_to_ext(no_8_tashes) -> false;
  1238. desk_error_to_ext(no_okey_discarded) -> {error, there_is_no_okey_there};
  1239. desk_error_to_ext(not_your_order) -> {error, not_your_turn};
  1240. desk_error_to_ext(blocked) -> {error, okey_is_blocked};
  1241. desk_error_to_ext(no_tash) -> {error, no_tash};
  1242. desk_error_to_ext(no_such_tash) -> {error, no_such_tash};
  1243. desk_error_to_ext(hand_not_match) -> {error, discarded_hand_does_not_match_server_state};
  1244. desk_error_to_ext(E) -> {error, E}.
  1245. %%===================================================================
  1246. get_timeout(turn, fast) -> {ok, Val} = kvs:get(config,"games/okey/turn_timeout_fast", 15000), Val;
  1247. get_timeout(turn, normal) -> {ok, Val} = kvs:get(config,"games/okey/turn_timeout_normal", 30000), Val;
  1248. get_timeout(turn, slow) -> {ok, Val} = kvs:get(config,"games/okey/turn_timeout_slow", 60000), Val;
  1249. get_timeout(challenge, fast) -> {ok, Val} = kvs:get(config,"games/okey/challenge_timeout_fast", 5000), Val;
  1250. get_timeout(challenge, normal) -> {ok, Val} = kvs:get(config,"games/okey/challenge_timeout_normal", 10000), Val;
  1251. get_timeout(challenge, slow) -> {ok, Val} = kvs:get(config,"games/okey/challenge_timeout_slow", 20000), Val;
  1252. get_timeout(ready, fast) -> {ok, Val} = kvs:get(config,"games/okey/ready_timeout_fast", 15000), Val;
  1253. get_timeout(ready, normal) -> {ok, Val} = kvs:get(config,"games/okey/ready_timeout_normal", 25000), Val;
  1254. get_timeout(ready, slow) -> {ok, Val} = kvs:get(config,"games/okey/ready_timeout_slow", 45000), Val.
  1255. %%===================================================================
  1256. calc_timeout(undefined) -> 0;
  1257. calc_timeout(TRef) ->
  1258. case erlang:read_timer(TRef) of
  1259. false -> 0;
  1260. Timeout -> Timeout
  1261. end.
  1262. calc_timeout_comp(TRef, Compensation) ->
  1263. if TRef == undefined -> null;
  1264. true -> case erlang:read_timer(TRef) of
  1265. false -> 0;
  1266. T when T < Compensation -> 0; %% Latency time compensation
  1267. T -> T - Compensation
  1268. end
  1269. end.
  1270. %%===================================================================
  1271. next_seat_num(?SEATS_NUM) -> 1;
  1272. next_seat_num(N) -> N + 1.
  1273. prev_seat_num(1) -> ?SEATS_NUM;
  1274. prev_seat_num(N) -> N - 1.
  1275. %%===================================================================
  1276. init_desk_state(Desk) ->
  1277. SeatsNums = ?DESK:get_seats_nums(Desk),
  1278. Hands = [{SeatNum, ?DESK:get_hand(Desk, SeatNum)} || SeatNum <- SeatsNums],
  1279. Discarded = [{SeatNum, ?DESK:get_discarded(Desk, SeatNum)} || SeatNum <- SeatsNums],
  1280. #desk_state{state = ?DESK:get_state_name(Desk),
  1281. hands = Hands,
  1282. discarded = Discarded,
  1283. deck = ?DESK:get_deck(Desk),
  1284. cur_seat = ?DESK:get_cur_seat(Desk),
  1285. gosterge = ?DESK:get_gosterge(Desk),
  1286. have_8_tashes = ?DESK:get_have_8_tashes(Desk),
  1287. has_gosterge = ?DESK:get_has_gosterge(Desk)}.
  1288. %%===================================================================
  1289. choose_gosterge(Deck) ->
  1290. Pos = crypto:rand_uniform(1, deck:size(Deck)),
  1291. case deck:get(Pos, Deck) of
  1292. {false_okey, _} -> choose_gosterge(Deck);
  1293. {Gosterge, Deck1} -> {Gosterge, Deck1}
  1294. end.