okey_table.erl 71 KB

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