okey_table.erl 71 KB

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